mirror of
https://github.com/fluxerapp/fluxer
synced 2026-10-17 13:05:32 +09:00
fix(api): send correct staff emails and allow suppressing them (#3048)
This commit is contained in:
1 parent
f0b3c82cfd
commit
39f9beda5a
113 files changed
+2496
-206
No files matched your search
@@ -227,6 +227,13 @@ pub fn checkbox(name: &str, value: &str, label: &str, checked: bool, enabled: bo
|
||||
}
|
||||
}
|
||||
|
||||
pub fn opt_out_checkbox(name: &str, label: &str) -> Markup {
|
||||
html! {
|
||||
input type="hidden" name={(name) "_present"} value="1";
|
||||
(checkbox(name, "true", label, true, true))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn secondary_button_link(label: &str, href: &str) -> Markup {
|
||||
html! {
|
||||
a href=(href) role="button"
|
||||
|
||||
@@ -8,7 +8,8 @@ use crate::{
|
||||
components::{
|
||||
form::{
|
||||
FORM_SELECT_CLASS, checkbox, csrf_input, danger_button, form_actions,
|
||||
form_field_group, select_chevron, submit_button, text_input, textarea_input,
|
||||
form_field_group, opt_out_checkbox, select_chevron, submit_button, text_input,
|
||||
textarea_input,
|
||||
},
|
||||
page_container::page_header,
|
||||
section_card::section_card_simple,
|
||||
@@ -426,6 +427,7 @@ fn bulk_schedule_deletion_section(base: &str, csrf_token: &str) -> Markup {
|
||||
},
|
||||
))
|
||||
(text_input("audit_log_reason", "Audit Log Reason (optional)", "", "Reason for this bulk operation"))
|
||||
(opt_out_checkbox("notify_user", "Email each user about the scheduled deletion"))
|
||||
(form_actions(html! {
|
||||
(danger_button("Schedule Deletion"))
|
||||
}))
|
||||
@@ -484,6 +486,13 @@ mod tests {
|
||||
assert!(markup.contains(r#"name="days_until_deletion" value="60" min="14" max="365""#));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deletion_form_emails_each_user_by_default() {
|
||||
let markup = bulk_schedule_deletion_section("/admin", "csrf").into_string();
|
||||
assert!(markup.contains(r#"name="notify_user" value="true" checked"#));
|
||||
assert!(markup.contains(r#"name="notify_user_present" value="1""#));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn remove_grid_can_clear_the_deprecated_clone_features() {
|
||||
let markup = guild_feature_checkbox_grid("remove_features[]", true).into_string();
|
||||
|
||||
@@ -9,7 +9,7 @@ use crate::{
|
||||
components::{
|
||||
badge::{BadgeVariant, badge},
|
||||
data_field::{data_field, data_field_link_mono, data_field_mono, data_field_text},
|
||||
form::csrf_input,
|
||||
form::{csrf_input, opt_out_checkbox},
|
||||
media::{guild_icon_url, initials, user_avatar_url},
|
||||
message_data::ordered_messages,
|
||||
message_list::{message_deletion_script, message_list},
|
||||
@@ -376,16 +376,7 @@ fn actions_card(config: &AdminConfig, report: &ReportEntry, csrf_token: &str) ->
|
||||
(section_card(Some("Actions"), None, None, html! {
|
||||
div class="flex flex-col gap-3" {
|
||||
@if report.status == 0 {
|
||||
form method="post"
|
||||
action={(base) "/reports/" (&report.report_id) "/resolve"} {
|
||||
(csrf_input(csrf_token))
|
||||
button type="submit"
|
||||
class="inline-flex w-full items-center justify-center gap-2 \
|
||||
font-medium rounded-lg bg-neutral-900 text-white \
|
||||
px-4 py-2 text-sm" {
|
||||
"Resolve Report"
|
||||
}
|
||||
}
|
||||
(resolve_report_form(base, &report.report_id, csrf_token))
|
||||
}
|
||||
@if report.report_type == 0 || report.report_type == 1 {
|
||||
@if let Some(ref reported_id) = report.reported_user_id {
|
||||
@@ -410,6 +401,29 @@ fn actions_card(config: &AdminConfig, report: &ReportEntry, csrf_token: &str) ->
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_report_form(base: &str, report_id: &str, csrf_token: &str) -> Markup {
|
||||
html! {
|
||||
form method="post" action={(base) "/reports/" (report_id) "/resolve"} class="flex flex-col gap-3" {
|
||||
(csrf_input(csrf_token))
|
||||
label for="resolution" class="block text-sm font-medium text-neutral-700" {
|
||||
"Public comment to the reporter (optional)"
|
||||
}
|
||||
textarea id="resolution" name="resolution" rows="3" maxlength="512"
|
||||
class="block w-full rounded-md border border-neutral-300 \
|
||||
px-3 py-2 text-sm shadow-sm \
|
||||
focus:border-brand-primary focus:outline-none \
|
||||
focus:ring-1 focus:ring-brand-primary" {}
|
||||
(opt_out_checkbox("notify_reporter", "Notify the reporter by DM and email"))
|
||||
button type="submit"
|
||||
class="inline-flex w-full items-center justify-center gap-2 \
|
||||
font-medium rounded-lg bg-neutral-900 text-white \
|
||||
px-4 py-2 text-sm" {
|
||||
"Resolve Report"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn nav_link(href: &str, label: &str) -> Markup {
|
||||
html! {
|
||||
a href=(href)
|
||||
@@ -532,3 +546,18 @@ fn basic_info_section_fragment(config: &AdminConfig, report: &ReportEntry) -> Ma
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn resolve_form_offers_a_public_comment_and_notifies_the_reporter_by_default() {
|
||||
let markup = resolve_report_form("/admin", "1500000000000000001", "csrf").into_string();
|
||||
assert!(markup.contains(r#"action="/admin/reports/1500000000000000001/resolve""#));
|
||||
assert!(markup.contains(r#"name="resolution""#));
|
||||
assert!(markup.contains(r#"maxlength="512""#));
|
||||
assert!(markup.contains(r#"name="notify_reporter" value="true" checked"#));
|
||||
assert!(markup.contains(r#"name="notify_reporter_present" value="1""#));
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,9 @@ use crate::{
|
||||
},
|
||||
config::AdminConfig,
|
||||
templates::components::{
|
||||
form::{checkbox, csrf_input, danger_button, form_actions, submit_button},
|
||||
form::{
|
||||
checkbox, csrf_input, danger_button, form_actions, opt_out_checkbox, submit_button,
|
||||
},
|
||||
page_container::card_with_header,
|
||||
},
|
||||
utils::timestamps::format_admin_timestamp,
|
||||
@@ -142,9 +144,26 @@ fn ban_actions_card(
|
||||
form method="post"
|
||||
action={(base) "/users/" (user.id) "?action=unban&tab=moderation"} {
|
||||
(csrf_input(csrf_token))
|
||||
(form_actions(html! {
|
||||
(submit_button("Unban User"))
|
||||
}))
|
||||
div class="space-y-3" {
|
||||
(form_label("Public Reason (optional, shown to the user)"))
|
||||
input type="text" name="public_reason"
|
||||
placeholder="Enter public unban reason..." maxlength="512"
|
||||
class="block w-full rounded-md border border-neutral-300 \
|
||||
px-3 py-2 text-sm shadow-sm \
|
||||
focus:border-brand-primary focus:outline-none \
|
||||
focus:ring-1 focus:ring-brand-primary";
|
||||
(form_label("Private Reason (optional, audit log)"))
|
||||
input type="text" name="private_reason"
|
||||
placeholder="Enter private unban reason (audit log)..."
|
||||
class="block w-full rounded-md border border-neutral-300 \
|
||||
px-3 py-2 text-sm shadow-sm \
|
||||
focus:border-brand-primary focus:outline-none \
|
||||
focus:ring-1 focus:ring-brand-primary";
|
||||
(opt_out_checkbox("notify_user", "Email the user that the suspension was lifted"))
|
||||
(form_actions(html! {
|
||||
(submit_button("Unban User"))
|
||||
}))
|
||||
}
|
||||
}
|
||||
} @else {
|
||||
form method="post"
|
||||
@@ -163,7 +182,7 @@ fn ban_actions_card(
|
||||
}
|
||||
(form_label("Public Reason (optional)"))
|
||||
input type="text" name="reason"
|
||||
placeholder="Enter public ban reason..."
|
||||
placeholder="Enter public ban reason..." maxlength="512"
|
||||
class="block w-full rounded-md border border-neutral-300 \
|
||||
px-3 py-2 text-sm shadow-sm \
|
||||
focus:border-brand-primary focus:outline-none \
|
||||
@@ -175,6 +194,7 @@ fn ban_actions_card(
|
||||
px-3 py-2 text-sm shadow-sm \
|
||||
focus:border-brand-primary focus:outline-none \
|
||||
focus:ring-1 focus:ring-brand-primary";
|
||||
(opt_out_checkbox("notify_user", "Email the user about this suspension (temporary bans only)"))
|
||||
(form_actions(html! {
|
||||
(submit_button("Ban/Suspend User"))
|
||||
}))
|
||||
@@ -365,7 +385,7 @@ fn deletion_card(
|
||||
}
|
||||
(form_label("Public Reason (optional)"))
|
||||
input type="text" name="public_reason"
|
||||
placeholder="Enter public reason..."
|
||||
placeholder="Enter public reason..." maxlength="512"
|
||||
class="block w-full rounded-md border border-neutral-300 \
|
||||
px-3 py-2 text-sm shadow-sm \
|
||||
focus:border-brand-primary focus:outline-none \
|
||||
@@ -377,6 +397,7 @@ fn deletion_card(
|
||||
px-3 py-2 text-sm shadow-sm \
|
||||
focus:border-brand-primary focus:outline-none \
|
||||
focus:ring-1 focus:ring-brand-primary";
|
||||
(opt_out_checkbox("notify_user", "Email the user about the scheduled deletion"))
|
||||
(form_actions(html! {
|
||||
(submit_button("Schedule Deletion"))
|
||||
}))
|
||||
@@ -778,6 +799,33 @@ mod tests {
|
||||
assert!(!markup.contains("replace_pending_deletion_at"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn schedule_form_emails_the_user_by_default() {
|
||||
let markup = deletion_card("/admin", &user(json!({})), "csrf", None).into_string();
|
||||
assert!(markup.contains(r#"name="notify_user" value="true" checked"#));
|
||||
assert!(markup.contains(r#"name="notify_user_present" value="1""#));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn temp_ban_form_emails_the_user_by_default() {
|
||||
let markup = ban_actions_card("/admin", &user(json!({})), "csrf", None).into_string();
|
||||
assert!(markup.contains(r#"name="notify_user" value="true" checked"#));
|
||||
assert!(markup.contains(r#"name="notify_user_present" value="1""#));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unban_form_separates_the_public_and_private_reasons() {
|
||||
let target = user(json!({"temp_banned_until": "2026-10-01T00:00:00.000Z"}));
|
||||
let markup = ban_actions_card("/admin", &target, "csrf", None).into_string();
|
||||
assert!(markup.contains("?action=unban&tab=moderation"));
|
||||
assert!(markup.contains(r#"name="notify_user" value="true" checked"#));
|
||||
assert!(markup.contains(r#"name="notify_user_present" value="1""#));
|
||||
assert!(markup.contains(
|
||||
r#"name="public_reason" placeholder="Enter public unban reason..." maxlength="512""#
|
||||
));
|
||||
assert!(markup.contains(r#"name="private_reason""#));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn current_ban_is_the_entry_matching_the_ban_end_and_notes_attach_to_it() {
|
||||
let target = user(json!({"temp_banned_until": "2026-10-01T00:00:00.000Z"}));
|
||||
|
||||
Reference in new issue
Block a user