frontend:add:error handling on failed validation
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
import { page } from "$app/stores";
|
||||
import { receive, send } from "$lib/utils/helpers";
|
||||
import { t } from "svelte-i18n";
|
||||
import { fly } from "svelte/transition";
|
||||
|
||||
$: ({ user } = $page.data);
|
||||
|
||||
@@ -19,33 +20,19 @@
|
||||
/** @type{Avatar[]} */
|
||||
let avatars = [];
|
||||
|
||||
/**
|
||||
* @typedef {Object} FormData
|
||||
* @property {string} first_name
|
||||
* @property {string} last_name
|
||||
* @property {string} email
|
||||
* @property {string} [password]
|
||||
* @property {string} [password2]
|
||||
* @property {string} [phone]
|
||||
* @property {string} address
|
||||
* @property {string} zip_code
|
||||
* @property {string} city
|
||||
*/
|
||||
/**
|
||||
* @typedef {Object.<string, string>} ValidationErrors
|
||||
*/
|
||||
/**
|
||||
* @type {ValidationErrors}
|
||||
*/
|
||||
let validationErrors = {};
|
||||
|
||||
const TABS = ["profile", "membership", "bankaccount"];
|
||||
let activeTab = TABS[0];
|
||||
|
||||
/** @type{string[]} */
|
||||
let errorMessages = [];
|
||||
|
||||
let showModal = false,
|
||||
isUploading = false,
|
||||
isUpdating = false,
|
||||
showAvatars = false;
|
||||
showAvatars = false,
|
||||
password = "",
|
||||
password2 = "";
|
||||
|
||||
const open = () => (showModal = true);
|
||||
const close = () => (showModal = false);
|
||||
const toggleAvatars = () => (showAvatars = !showAvatars);
|
||||
@@ -84,62 +71,30 @@
|
||||
activeTab = tab;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates thek form data
|
||||
* @param {Object} data - The form data to validate
|
||||
* @returns {ValidationErrors} An object containing validation errors
|
||||
*/
|
||||
function validateForm(data) {
|
||||
/** @type {ValidationErrors} */
|
||||
let errors = {};
|
||||
|
||||
if ("first_name" in data && !String(data.first_name).trim()) {
|
||||
errors.first_name = $t("required");
|
||||
}
|
||||
if ("last_name" in data && !String(data.last_name).trim()) {
|
||||
errors.last_name = $t("required");
|
||||
}
|
||||
if (
|
||||
"email" in data &&
|
||||
(!data.email || !/^\S+@\S+\.\S+$/.test(String(data.email)))
|
||||
) {
|
||||
errors.email = $t("required");
|
||||
}
|
||||
if ("password" in data) {
|
||||
if (String(data.password).length < 8) {
|
||||
errors.password = $t("required_password");
|
||||
}
|
||||
if ("password2" in data && data.password !== data.password2) {
|
||||
errors.password2 = $t("required_password_match");
|
||||
}
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
/** @type {import('./$types').SubmitFunction} */
|
||||
const handleUpdate = async ({ form, formData, action, cancel }) => {
|
||||
/** @type {Object.<string, FormDataEntryValue>} */
|
||||
const fd = Object.fromEntries(formData);
|
||||
validationErrors = validateForm(fd);
|
||||
if (Object.keys(validationErrors).length > 0) {
|
||||
errorMessages = [];
|
||||
const errorElements = form.querySelectorAll(".error-message");
|
||||
|
||||
errorElements.forEach((element) => {
|
||||
if (element.textContent) {
|
||||
errorMessages.push(element.textContent);
|
||||
}
|
||||
});
|
||||
|
||||
if (errorMessages.length > 0) {
|
||||
cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
errorMessages = [];
|
||||
isUpdating = true;
|
||||
return async ({ result }) => {
|
||||
isUpdating = false;
|
||||
if (result.type === "success" || result.type === "redirect") {
|
||||
validationErrors = {};
|
||||
close();
|
||||
} else if (result.type == "failure" && result.data?.errors) {
|
||||
/** @type {ValidationErrors} */
|
||||
validationErrors = {};
|
||||
// Assuming result.data.errors is an array of {error: string, id: string}
|
||||
result.data.errors.forEach(({ error, id }) => {
|
||||
validationErrors[id] = error;
|
||||
});
|
||||
errorMessages = result.data.errors.map((error) => error.error);
|
||||
}
|
||||
await applyAction(result);
|
||||
};
|
||||
@@ -345,12 +300,16 @@
|
||||
type="password"
|
||||
label={$t("password")}
|
||||
placeholder={$t("placeholder_password")}
|
||||
bind:value={password}
|
||||
otherPasswordValue={password2}
|
||||
/>
|
||||
<InputField
|
||||
name="password2"
|
||||
type="password"
|
||||
label={$t("password_repeat")}
|
||||
placeholder={$t("placeholder_password")}
|
||||
bind:value={password2}
|
||||
otherPasswordValue={password}
|
||||
/>
|
||||
<InputField
|
||||
name="first_name"
|
||||
@@ -452,12 +411,14 @@
|
||||
label={$t("iban")}
|
||||
value={user.bank_account?.iban || ""}
|
||||
placeholder={$t("placeholder_iban")}
|
||||
toUpperCase={true}
|
||||
/>
|
||||
<InputField
|
||||
name="bic"
|
||||
label={$t("bic")}
|
||||
value={user.bank_account?.bic || ""}
|
||||
placeholder={$t("placeholder_bic")}
|
||||
toUpperCase={true}
|
||||
/>
|
||||
<InputField
|
||||
name="mandate_reference"
|
||||
@@ -467,6 +428,15 @@
|
||||
/>
|
||||
</div>
|
||||
{/if}
|
||||
{#if errorMessages.length > 0}
|
||||
<div class="error-container">
|
||||
{#each errorMessages as message, i (i)}
|
||||
<p class="error-message" transition:fly={{ y: -20, duration: 300 }}>
|
||||
{message}
|
||||
</p>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
<div class="button-container">
|
||||
{#if isUpdating}
|
||||
<SmallLoader width={30} message={"Aktualisiere..."} />
|
||||
@@ -482,6 +452,19 @@
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
.error-container {
|
||||
background-color: #fee2e2;
|
||||
border: 1px solid #f87171;
|
||||
border-radius: 4px;
|
||||
padding: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.error-message {
|
||||
color: #dc2626;
|
||||
margin: 0.5rem 0;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
.tab-content {
|
||||
padding: 1rem;
|
||||
border-radius: 0 0 3px 3px;
|
||||
|
||||
Reference in New Issue
Block a user