Skip to content

Commit 1098969

Browse files
teelurCopilot
andcommitted
Email changes (#743)
* i should have broken up these commits * only send email once * some tweaks * Don't show email confirmation message when disabled * update deps * disconnect session afterwards * Update server/BudgetBoard.WebAPI/Utils/Helpers.cs Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update server/BudgetBoard.WebAPI/BudgetBoard.WebAPI.csproj Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Only run when enabled --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 305370f commit 1098969

File tree

18 files changed

+1627
-1512
lines changed

18 files changed

+1627
-1512
lines changed

client/package.json

Lines changed: 34 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -10,63 +10,63 @@
1010
"preview": "vite preview"
1111
},
1212
"dependencies": {
13-
"@dnd-kit/helpers": "^0.1.21",
14-
"@dnd-kit/react": "^0.1.21",
15-
"@icons-pack/react-simple-icons": "^13.8.0",
16-
"@mantine/carousel": "^8.3.10",
17-
"@mantine/charts": "^8.3.10",
18-
"@mantine/code-highlight": "^8.3.10",
19-
"@mantine/core": "^8.3.10",
20-
"@mantine/dates": "^8.3.10",
21-
"@mantine/dropzone": "^8.3.10",
22-
"@mantine/form": "^8.3.10",
23-
"@mantine/hooks": "^8.3.10",
24-
"@mantine/modals": "^8.3.10",
25-
"@mantine/notifications": "^8.3.10",
26-
"@mantine/nprogress": "^8.3.10",
27-
"@mantine/spotlight": "^8.3.10",
28-
"@tanstack/react-query": "^5.90.12",
13+
"@dnd-kit/helpers": "^0.3.2",
14+
"@dnd-kit/react": "^0.3.2",
15+
"@icons-pack/react-simple-icons": "^13.11.2",
16+
"@mantine/carousel": "^8.3.15",
17+
"@mantine/charts": "^8.3.15",
18+
"@mantine/code-highlight": "^8.3.15",
19+
"@mantine/core": "^8.3.15",
20+
"@mantine/dates": "^8.3.15",
21+
"@mantine/dropzone": "^8.3.15",
22+
"@mantine/form": "^8.3.15",
23+
"@mantine/hooks": "^8.3.15",
24+
"@mantine/modals": "^8.3.15",
25+
"@mantine/notifications": "^8.3.15",
26+
"@mantine/nprogress": "^8.3.15",
27+
"@mantine/spotlight": "^8.3.15",
28+
"@tanstack/react-query": "^5.90.21",
2929
"@types/papaparse": "^5.5.2",
3030
"axios": "^1.13.5",
3131
"dayjs": "^1.11.19",
3232
"embla-carousel-react": "8.6.0",
33-
"i18next": "^25.7.2",
33+
"i18next": "^25.8.13",
3434
"i18next-http-backend": "^3.0.2",
35-
"lucide-react": "^0.561.0",
35+
"lucide-react": "^0.575.0",
3636
"papaparse": "^5.5.3",
3737
"qrcode.react": "^4.2.0",
38-
"react": "^19.2.3",
39-
"react-dom": "^19.2.3",
40-
"react-i18next": "^16.5.0",
41-
"react-router": "^7.12.0",
42-
"recharts": "3.5.1"
38+
"react": "^19.2.4",
39+
"react-dom": "^19.2.4",
40+
"react-i18next": "^16.5.4",
41+
"react-router": "^7.13.0",
42+
"recharts": "3.7.0"
4343
},
4444
"devDependencies": {
4545
"@eslint/js": "^9.39.2",
46-
"@tanstack/eslint-plugin-query": "^5.91.2",
47-
"@tanstack/react-query-devtools": "^5.91.1",
48-
"@types/node": "^25.0.2",
49-
"@types/react": "^19.2.7",
46+
"@tanstack/eslint-plugin-query": "^5.91.4",
47+
"@tanstack/react-query-devtools": "^5.91.3",
48+
"@types/node": "^25.3.0",
49+
"@types/react": "^19.2.14",
5050
"@types/react-dom": "^19.2.3",
51-
"@vitejs/plugin-react": "^5.1.2",
51+
"@vitejs/plugin-react": "^5.1.4",
5252
"babel-plugin-react-compiler": "^1.0.0",
53-
"eslint": "^9.39.2",
53+
"eslint": "^9.39.3",
5454
"eslint-config-mantine": "^4.0.3",
5555
"eslint-plugin-i18n-json": "^4.0.1",
56-
"eslint-plugin-jsonc": "^2.21.0",
56+
"eslint-plugin-jsonc": "^2.21.1",
5757
"eslint-plugin-jsx-a11y": "^6.10.2",
5858
"eslint-plugin-react": "^7.37.5",
5959
"eslint-plugin-react-compiler": "^19.1.0-rc.2",
6060
"eslint-plugin-react-hooks": "^7.0.1",
61-
"eslint-plugin-react-refresh": "^0.4.25",
61+
"eslint-plugin-react-refresh": "^0.4.26",
6262
"globals": "^16.5.0",
6363
"postcss": "^8.5.6",
6464
"postcss-preset-mantine": "^1.18.0",
6565
"postcss-simple-vars": "^7.0.1",
66-
"react-scan": "^0.4.3",
66+
"react-scan": "^0.5.3",
6767
"typescript": "^5.9.3",
68-
"typescript-eslint": "^8.49.0",
69-
"vite": "^7.2.7"
68+
"typescript-eslint": "^8.56.0",
69+
"vite": "^7.3.1"
7070
},
7171
"resolutions": {
7272
"react-is": "^19.0.0"

client/public/locales/de/translation.json

Lines changed: 443 additions & 442 deletions
Large diffs are not rendered by default.

client/public/locales/en-us/translation.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
"a_simple_app_for_managing_monthly_budgets": "A simple app for managing monthly budgets.",
1515
"account": "Account",
1616
"account_column_is_required_message": "The account column is required.",
17-
"account_created_email_verification_message": "Account created. Check your email for a verification message.",
17+
"account_created_check_your_email_message": " Check your email for a verification message.",
18+
"account_created_message": "Account created.",
1819
"account_created_successfully_message": "Account created successfully.",
1920
"account_details": "Account Details",
2021
"account_is_required": "Account is required.",
@@ -250,6 +251,7 @@
250251
"linked_account_styled": "<0>Linked Account:</0><1>{{accountName}}</1>",
251252
"load_csv": "Load CSV",
252253
"login": "Login",
254+
"login_account_locked_message": "Your account is locked due to multiple failed login attempts. Please try again later or reset your password.",
253255
"login_account_not_verified_message": "Your account is not verified. Please check your email for a verification link.",
254256
"login_failed_message": "Login failed. Please check your credentials and try again.",
255257
"login_with_oidc": "Login with OIDC",
@@ -336,6 +338,7 @@
336338
"remove_lunchflow": "Remove LunchFlow",
337339
"remove_simplefin": "Remove SimpleFIN",
338340
"reorder": "Reorder",
341+
"resend": "Resend",
339342
"reset_code": "Reset Code",
340343
"reset_code_required_message": "Reset code is required to reset your password.",
341344
"reset_password": "Reset Password",
@@ -435,6 +438,8 @@
435438
"value_history": "Value History",
436439
"value_trends": "Value Trends",
437440
"values": "Values",
441+
"verification_email_resent_error_message": "There was an error sending the verification email.",
442+
"verification_email_resent_message": "Verification email resent.",
438443
"view_and_restore_deleted_accounts": "View and restore deleted accounts.",
439444
"view_and_restore_deleted_assets": "View and restore deleted assets.",
440445
"view_and_restore_deleted_transactions": "View and restore deleted transactions.",
Lines changed: 126 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,128 @@
11
{
2-
"10_months": "10 Mois",
3-
"11_months": "11 Mois",
4-
"12_months": "12 Mois",
5-
"1_month": "1 Mois",
6-
"2_months": "2 Mois",
7-
"3_months": "3 Mois",
8-
"4_months": "4 Mois",
9-
"5_months": "5 Mois",
10-
"6_months": "6 Mois",
11-
"7_months": "7 Mois",
12-
"8_months": "8 Mois",
13-
"9_months": "9 Mois",
14-
"a_simple_app_for_managing_monthly_budgets": "Une application simple pour gérer vos budgets mensuels.",
15-
"account": "Compte",
16-
"account_column_is_required_message": "La colonne « Compte » est obligatoire.",
17-
"account_created_email_verification_message": "Compte créé. Vérifiez votre boîte mail pour trouver le message de vérification.",
18-
"account_created_successfully_message": "Compte créé avec succès.",
19-
"account_details": "Détails du compte",
20-
"account_is_required": "Un compte est nécessaire.",
21-
"account_mapping": "Cartographie des comptes",
22-
"account_name": "Nom du compte",
23-
"account_trends": "Tendances du compte",
24-
"account_type": "Type de compte",
25-
"accounts": "Comptes",
26-
"accounts_settings": "Paramètres des comptes",
27-
"add_balance": "Ajouter un solde",
28-
"add_category": "Ajouter une catégorie"
2+
"10_months": "10 Mois",
3+
"11_months": "11 Mois",
4+
"12_months": "12 Mois",
5+
"1_month": "1 Mois",
6+
"2_months": "2 Mois",
7+
"3_months": "3 Mois",
8+
"4_months": "4 Mois",
9+
"5_months": "5 Mois",
10+
"6_months": "6 Mois",
11+
"7_months": "7 Mois",
12+
"8_months": "8 Mois",
13+
"9_months": "9 Mois",
14+
"a_simple_app_for_managing_monthly_budgets": "Une application simple pour gérer les budgets mensuels.",
15+
"account": "Compte",
16+
"account_column_is_required_message": "La colonne « Compte » est obligatoire.",
17+
"account_created_check_your_email_message": " Vérifier votre email pour un message de confirmation.",
18+
"account_created_message": "Le compte a bien été créé.",
19+
"account_created_successfully_message": "Compte créé avec succès.",
20+
"account_details": "Détails du compte",
21+
"account_is_required": "Un compte est nécessaire.",
22+
"account_mapping": "Cartographie des comptes",
23+
"account_name": "Nom du compte",
24+
"account_trends": "Tendances du compte",
25+
"account_type": "Type de compte",
26+
"accounts": "Comptes",
27+
"accounts_settings": "Paramètres des comptes",
28+
"add_balance": "Ajouter un solde",
29+
"add_category": "Ajouter une catégorie",
30+
"add_goal": "Ajouter un objectif",
31+
"add_rule": "Ajouter une règle",
32+
"add_value": "Ajouter une valeur",
33+
"advanced_settings": "Paramètres avancés",
34+
"after": "Après",
35+
"all": "Tout",
36+
"all_children_will_also_be_deleted": "Tous les éléments enfants seront également supprimés.",
37+
"amount": "Montant",
38+
"amount_format": "Format du montant",
39+
"appearance_mode": "Mode d’apparence",
40+
"apply_existing_account_amount_to_goal": "Appliquer le solde existant du compte à l’objectif cible ?",
41+
"asset": "Actif",
42+
"asset_deleted_successfully_message": "Actif supprimé avec succès.",
43+
"asset_details": "Détails de l’actif",
44+
"asset_name": "Nom de l’actif",
45+
"asset_restored_successfully_message": "Actif restauré avec succès.",
46+
"assets": "Actifs",
47+
"assets_settings": "Paramètres des actifs",
48+
"authorization_code_missing_message": "Le code d’autorisation est manquant dans l’URL de rappel.",
49+
"auto": "Auto",
50+
"auto_categorizer_minimum_probability": "Probabilité minimale de catégorisation automatique",
51+
"auto_categorizer_minimum_probability_description": "Définissez le seuil minimal de confiance (0 %–100 %) requis pour que le catégoriseur automatique attribue une catégorie. Des valeurs plus élevées signifient qu’un niveau de certitude plus important est nécessaire.",
52+
"automatic_rules": "Règles automatiques",
53+
"automatic_rules_description": "Créez des règles qui mettent automatiquement à jour les champs lors de la synchronisation lorsque les conditions spécifiées sont remplies.",
54+
"average_income": "Revenu moyen",
55+
"average_spending": "Dépenses moyennes",
56+
"back": "Retour",
57+
"back_to_login": "Retour à la connexion",
58+
"balance_deleted_successfully_message": "Solde supprimé avec succès.",
59+
"balance_restored_successfully_message": "Solde restauré avec succès.",
60+
"before": "Avant",
61+
"both_income_and_expense_values_present_message": "Des valeurs de revenu et de dépense sont toutes deux présentes pour la ligne {{uid}}.",
62+
"budget_amount_fraction_editable_total_styled": "<0>{{amount}}</0> <1>de</1>",
63+
"budget_amount_fraction_no_total_styled": "<0>{{amount}}</0>",
64+
"budget_amount_fraction_styled": "<0>{{amount}}</0> <1>de</1> <2>{{total}}</2>",
65+
"budget_copy_failed_message": "Une erreur s’est produite lors de la copie du budget du mois précédent.",
66+
"budget_details": "Détails du budget",
67+
"budget_left_styled": "<0>{{amount}}</0> <1>gauche</1>",
68+
"budget_monthly_amount_fraction_editable_styled": "<0>{{amount}}</0> <1>de</1>",
69+
"budget_monthly_amount_fraction_styled": "<0>{{amount}}</0> <1>de</1> <2>{{total}}</2> <1>ce mois</1>",
70+
"budget_previous_month_no_budgets": "Le mois précédent ne contient aucun budget.",
71+
"budget_projected_editable_styled": "<0>Prévisionnel :</0>",
72+
"budget_projected_styled": "<0>Prévisionnel :</0> <1>{{amount}}</1>",
73+
"budget_settings": "Paramètres du budget",
74+
"budget_summary": "Résumé du budget",
75+
"budget_warning_threshold": "Seuil d’alerte du budget",
76+
"budget_warning_threshold_description": "Définissez le seuil de pourcentage à partir duquel les budgets deviendront jaunes.",
77+
"budget_warning_threshold_invalid_message": "Le seuil d’alerte doit être compris entre 0 et 100.",
78+
"budgets": "Budgets",
79+
"built_in_transaction_categories": "Catégories de transactions intégrées",
80+
"cancel": "Annuler",
81+
"category": "Catégorie",
82+
"category_deleted_successfully_message": "Catégorie supprimée avec succès.",
83+
"category_name": "Nom de la catégorie",
84+
"category_type": "Type de catégorie",
85+
"child": "Enfant",
86+
"clear_all": "Tout effacer",
87+
"clear_filters": "Effacer les filtres",
88+
"clear_selection": "Effacer la sélection",
89+
"close": "Fermer",
90+
"code_copied_to_clipboard": "Code copié dans le presse-papiers.",
91+
"columns_fields": "Champs des colonnes",
92+
"columns_options": "Options des colonnes",
93+
"columns_to_match": "Colonnes à faire correspondre",
94+
"complete_date": "Date d’achèvement",
95+
"completed_goals": "Objectifs atteints",
96+
"configure_transactions": "Configurer les transactions",
97+
"confirm_delete_budget_message": "Êtes-vous sûr de vouloir supprimer ce budget ?",
98+
"confirm_new_password": "Confirmer le nouveau mot de passe",
99+
"confirm_password": "Confirmer le mot de passe",
100+
"connected": "Connecté",
101+
"contains": "Contient",
102+
"copy_key": "Copier la clé",
103+
"copy_previous": "Copier le précédent",
104+
"copy_recovery_codes": "Copier les codes de récupération",
105+
"create_a_goal_with_a_specified": "Créer un objectif avec un(e) :",
106+
"create_account": "Créer un compte",
107+
"create_asset": "Créer un actif",
108+
"create_goal": "Créer un objectif",
109+
"create_password": "Créer un mot de passe",
110+
"create_transaction": "Créer une transaction",
111+
"csv_file_is_empty_message": "Le fichier CSV est vide.",
112+
"csv_file_missing_header_row_message": "Le fichier CSV ne contient pas de ligne d’en-tête.",
113+
"current_password": "Mot de passe actuel",
114+
"custom_categories": "Catégories personnalisées",
115+
"custom_categories_description": "Créez des catégories personnalisées pour organiser vos transactions.",
116+
"dark": "Sombre",
117+
"dashboard": "Tableau de bord",
118+
"date": "Date",
119+
"date_format": "Format de date",
120+
"date_format_description": "Spécifiez le format des dates dans votre fichier CSV.",
121+
"date_is_required": "La date est requise.",
122+
"date_range": "Plage de dates",
123+
"days_since_deleted": "{{days}} jours depuis la suppression",
124+
"de": "Allemand",
125+
"decimal_separator": "Séparateur décimal",
126+
"decimal_separator_required_message": "Le séparateur décimal est requis.",
127+
"delete": "Supprimer"
29128
}

client/src/app/Unauthorized/Login/Login.tsx

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
1-
import { Anchor, Button, LoadingOverlay, Stack, Divider } from "@mantine/core";
1+
import {
2+
Anchor,
3+
Button,
4+
LoadingOverlay,
5+
Stack,
6+
Divider,
7+
Group,
8+
} from "@mantine/core";
29
import { hasLength, isEmail, useField } from "@mantine/form";
310
import React from "react";
411
import { LoginCardState } from "../Welcome";
@@ -38,7 +45,7 @@ const Login = (props: LoginProps): React.ReactNode => {
3845
{ min: passwordMinLength },
3946
t("password_min_length_message", {
4047
minLength: passwordMinLength,
41-
})
48+
}),
4249
),
4350
});
4451

@@ -47,6 +54,28 @@ const Login = (props: LoginProps): React.ReactNode => {
4754

4855
const queryClient = useQueryClient();
4956

57+
const doResendVerificationEmail = (): void => {
58+
request({
59+
url: "/api/resendConfirmationEmail",
60+
method: "POST",
61+
data: {
62+
email: emailField.getValue(),
63+
},
64+
})
65+
.then(() => {
66+
notifications.show({
67+
color: "var(--button-color-confirm)",
68+
message: t("verification_email_resent_message"),
69+
});
70+
})
71+
.catch(() => {
72+
notifications.show({
73+
color: "var(--button-color-destructive)",
74+
message: t("verification_email_resent_error_message"),
75+
});
76+
});
77+
};
78+
5079
const doLogin = async (): Promise<void> => {
5180
setLoading(true);
5281

@@ -79,12 +108,27 @@ const Login = (props: LoginProps): React.ReactNode => {
79108
.catch((error: AxiosError) => {
80109
// These error response values are specific to ASP.NET Identity,
81110
// so will do the error translation here.
82-
if ((error.response?.data as any)?.detail === "NotAllowed") {
111+
if ((error.response?.data as any)?.detail === "EmailNotVerifiedError") {
83112
notifications.show({
84113
color: "var(--button-color-destructive)",
85-
message: t("login_account_not_verified_message"),
114+
message: (
115+
<Group gap="1rem" wrap="nowrap">
116+
<div>{t("login_account_not_verified_message")}</div>
117+
<Button
118+
size="xs"
119+
miw="fit-content"
120+
onClick={doResendVerificationEmail}
121+
>
122+
{t("resend")}
123+
</Button>
124+
</Group>
125+
),
126+
autoClose: 10000,
86127
});
87-
} else if ((error.response?.data as any)?.detail === "Failed") {
128+
} else if (
129+
(error.response?.data as any)?.detail ===
130+
"InvalidEmailOrPasswordError"
131+
) {
88132
notifications.show({
89133
color: "var(--button-color-destructive)",
90134
message: t("login_failed_message"),

0 commit comments

Comments
 (0)