about page changes

This commit is contained in:
Alex
2024-09-29 21:30:21 +02:00
parent aea61e9440
commit 4bd10d24d2
2 changed files with 339 additions and 188 deletions

View File

@@ -1,6 +1,7 @@
import { BASE_API_URI } from "$lib/utils/constants"; import { BASE_API_URI } from "$lib/utils/constants";
import { formatError } from "$lib/utils/helpers"; import { formatError } from "$lib/utils/helpers";
import { fail, redirect } from "@sveltejs/kit"; import { fail, redirect } from "@sveltejs/kit";
import { toRFC3339 } from "$lib/utils/utils";
/** @type {import('./$types').PageServerLoad} */ /** @type {import('./$types').PageServerLoad} */
export async function load({ locals, params }) { export async function load({ locals, params }) {
@@ -21,19 +22,50 @@ export const actions = {
* @returns Error data or redirects user to the home page or the previous page * @returns Error data or redirects user to the home page or the previous page
*/ */
updateUser: async ({ request, fetch, cookies, locals }) => { updateUser: async ({ request, fetch, cookies, locals }) => {
const formData = await request.formData(); let formData = await request.formData();
/** @type {Record<string, string>} */
const updateData = {};
// Convert FormData to a plain object /** @type {Partial<App.Locals['user']>} */
formData.forEach((value, key) => { const updateData = {
if (typeof value === "string" && value !== "") { id: Number(formData.get("id")),
updateData[key] = value; first_name: String(formData.get("first_name")),
} last_name: String(formData.get("last_name")),
}); email: String(formData.get("email")),
phone: String(formData.get("phone")),
notes: String(formData.get("notes")),
address: String(formData.get("address")),
zip_code: String(formData.get("zip_code")),
city: String(formData.get("city")),
date_of_birth: toRFC3339(formData.get("birth_date")),
company: String(formData.get("company")),
profile_picture: String(formData.get("profile_picture")),
membership: {
id: Number(formData.get("membership_id")),
start_date: toRFC3339(formData.get("membership_start_date")),
end_date: toRFC3339(formData.get("membership_end_date")),
status: Number(formData.get("membership_status")),
parent_member_id: Number(formData.get("parent_member_id")),
subscription_model: {
id: Number(formData.get("subscription_model_id")),
name: String(formData.get("subscription_model_name")),
},
},
bank_account: {
id: Number(formData.get("bank_account_id")),
mandate_date_signed: String(formData.get("mandate_date_signed")),
bank: String(formData.get("bank")),
account_holder_name: String(formData.get("account_holder_name")),
iban: String(formData.get("iban")),
bic: String(formData.get("bic")),
mandate_reference: String(formData.get("mandate_reference")),
},
};
// Remove undefined or null properties
const cleanUpdateData = Object.fromEntries(
Object.entries(updateData).filter(([_, v]) => v != null)
);
console.dir(cleanUpdateData);
const apiURL = `${BASE_API_URI}/backend/users/update/`; const apiURL = `${BASE_API_URI}/backend/users/update/`;
const res = await fetch(apiURL, { const res = await fetch(apiURL, {
method: "PATCH", method: "PATCH",
credentials: "include", credentials: "include",
@@ -41,7 +73,7 @@ export const actions = {
"Content-Type": "application/json", "Content-Type": "application/json",
Cookie: `jwt=${cookies.get("jwt")}`, Cookie: `jwt=${cookies.get("jwt")}`,
}, },
body: JSON.stringify(updateData), body: JSON.stringify(cleanUpdateData),
}); });
if (!res.ok) { if (!res.ok) {
@@ -51,11 +83,11 @@ export const actions = {
} }
const response = await res.json(); const response = await res.json();
locals.user = response; locals.user = response;
// Format dates
if (locals.user.date_of_birth) { if (locals.user.date_of_birth) {
locals.user.date_of_birth = response["date_of_birth"].split("T")[0]; locals.user.date_of_birth = response.date_of_birth.split("T")[0];
} }
if (locals.user.membership?.start_date) { if (locals.user.membership?.start_date) {
locals.user.membership.start_date = locals.user.membership.start_date =
@@ -65,6 +97,7 @@ export const actions = {
locals.user.membership.end_date = locals.user.membership.end_date =
locals.user.membership.end_date.split("T")[0]; locals.user.membership.end_date.split("T")[0];
} }
throw redirect(303, `/auth/about/${response.id}`); throw redirect(303, `/auth/about/${response.id}`);
}, },
/** /**
@@ -102,6 +135,7 @@ export const actions = {
profile_picture: response[""], profile_picture: response[""],
}; };
}, },
/** /**
* *
* @param request - The request object * @param request - The request object

View File

@@ -11,7 +11,14 @@
import { t } from "svelte-i18n"; import { t } from "svelte-i18n";
import { fly } from "svelte/transition"; import { fly } from "svelte/transition";
$: ({ user } = $page.data); /** @type {import('./$types').ActionData} */
export let form;
/** @type {App.Locals['subscriptions']}*/
$: subscriptions = $page.data.subscriptions;
/** @type {App.Locals['user']}*/
$: user = $page.data.user;
/** @typedef {{name: string, src: string}} Avatar */ /** @typedef {{name: string, src: string}} Avatar */
const avatarFiles = import.meta.glob("$lib/img/Avatar-*.jpeg", { const avatarFiles = import.meta.glob("$lib/img/Avatar-*.jpeg", {
@@ -20,11 +27,38 @@
/** @type{Avatar[]} */ /** @type{Avatar[]} */
let avatars = []; let avatars = [];
const TABS = ["profile", "membership", "bankaccount"]; const TABS = ["profile", "licence", "membership", "bankaccount"];
let activeTab = TABS[0]; let activeTab = TABS[0];
/** @type{string[]} */ $: subscriptionModelOptions = subscriptions.map((sub) => ({
let errorMessages = []; value: sub?.name ?? "",
label: sub?.name ?? "",
}));
const userStatusOptions = [
{ value: 1, label: $t("userStatus.1"), color: "#b1b1b1" }, // Grey for "Nicht verifiziert"
{ value: 2, label: $t("userStatus.2"), color: "#90EE90" }, // Light green for "Verifiziert"
{ value: 3, label: $t("userStatus.3"), color: "#00bc00" }, // Green for "Aktiv"
{ value: 4, label: $t("userStatus.4"), color: "#FFC0CB" }, // Pink for "Passiv"
{ value: 5, label: $t("userStatus.5"), color: "#FF4646" }, // Red for "Deaktiviert"
];
const userRoleOptions = [
{ value: 0, label: $t("userRole.0"), color: "#b1b1b1" }, // Grey for "Mitglied"
{ value: 1, label: $t("userRole.1"), color: "#00bc00" }, // Green for "Betrachter"
{ value: 4, label: $t("userRole.4"), color: "#FFC0CB" }, // Pink for "Bearbeiter"
{ value: 8, label: $t("userRole.8"), color: "#FF4646" }, // Red for "Admin"
];
const membershipStatusOptions = [
{ value: 3, label: $t("userStatus.3"), color: "#00bc00" }, // Green for "Aktiv"
{ value: 4, label: $t("userStatus.4"), color: "#FFC0CB" }, // Pink for "Passiv"
{ value: 5, label: $t("userStatus.5"), color: "#FF4646" }, // Red for "Deaktiviert"
];
const licenceStatusOptions = [
{ value: 3, label: $t("userStatus.3"), color: "#00bc00" }, // Green for "Aktiv"
{ value: 4, label: $t("userStatus.4"), color: "#FFC0CB" }, // Pink for "Passiv"
{ value: 5, label: $t("userStatus.5"), color: "#FF4646" }, // Red for "Deaktiviert"
];
let showModal = false, let showModal = false,
isUploading = false, isUploading = false,
@@ -37,7 +71,13 @@
const close = () => (showModal = false); const close = () => (showModal = false);
const toggleAvatars = () => (showAvatars = !showAvatars); const toggleAvatars = () => (showAvatars = !showAvatars);
$: selectedSubscriptionModel =
subscriptions.find(
(sub) => sub?.id === user.membership?.subscription_model.id
) || null;
onMount(() => { onMount(() => {
console.dir(user);
avatars = Object.entries(avatarFiles).map(([path, module]) => { avatars = Object.entries(avatarFiles).map(([path, module]) => {
if (typeof path !== "string") { if (typeof path !== "string") {
throw new Error("Unexpected non-string path"); throw new Error("Unexpected non-string path");
@@ -60,9 +100,6 @@
}); });
}); });
/** @type {import('./$types').ActionData} */
export let form;
/** /**
* Sets the active tab * Sets the active tab
* @param {string} tab - The tab to set as active * @param {string} tab - The tab to set as active
@@ -73,28 +110,11 @@
/** @type {import('./$types').SubmitFunction} */ /** @type {import('./$types').SubmitFunction} */
const handleUpdate = async ({ form, formData, action, cancel }) => { const handleUpdate = async ({ form, formData, action, cancel }) => {
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; isUpdating = true;
return async ({ result }) => { return async ({ result }) => {
isUpdating = false; isUpdating = false;
if (result.type === "success" || result.type === "redirect") { if (result.type === "success" || result.type === "redirect") {
close(); close();
} else if (result.type == "failure" && result.data?.errors) {
errorMessages = result.data.errors.map((error) => error.error);
} }
await applyAction(result); await applyAction(result);
}; };
@@ -164,10 +184,10 @@
<span class="value">{user.phone}</span> <span class="value">{user.phone}</span>
</h3> </h3>
{/if} {/if}
{#if user.birth_date} {#if user.date_of_birth}
<h3 class="hero-subtitle subtitle info-row"> <h3 class="hero-subtitle subtitle info-row">
<span class="label">Geburtstag:</span> <span class="label">Geburtstag:</span>
<span class="value">{user.birth_date}</span> <span class="value">{user.date_of_birth}</span>
</h3> </h3>
{/if} {/if}
{#if user.notes} {#if user.notes}
@@ -256,6 +276,7 @@
method="POST" method="POST"
use:enhance={handleUpdate} use:enhance={handleUpdate}
> >
<input name="id" type="number" hidden bind:value={user.id} />
<h1 class="step-title" style="text-align: center;">{$t("user_edit")}</h1> <h1 class="step-title" style="text-align: center;">{$t("user_edit")}</h1>
{#if form?.success} {#if form?.success}
<h4 <h4
@@ -298,150 +319,238 @@
</button> </button>
{/each} {/each}
</div> </div>
{#if activeTab == "profile"} <div
<div class="tab-content"> class="tab-content"
style="display: {activeTab === 'profile' ? 'block' : 'none'}"
>
<InputField
name="status"
type="select"
label={$t("status")}
bind:value={user.status}
options={userStatusOptions}
/>
{#if user.role_id === 8}
<InputField <InputField
name="password" name="role_id"
type="password" type="select"
label={$t("password")} label={$t("user_role")}
placeholder={$t("placeholder_password")} bind:value={user.role_id}
bind:value={password} options={userRoleOptions}
otherPasswordValue={password2}
/> />
<InputField {/if}
name="password2" <InputField
type="password" name="password"
label={$t("password_repeat")} type="password"
placeholder={$t("placeholder_password")} label={$t("password")}
bind:value={password2} placeholder={$t("placeholder.password")}
otherPasswordValue={password} bind:value={password}
/> otherPasswordValue={password2}
<InputField />
name="first_name" <InputField
label={$t("first_name")} name="password2"
value={user.first_name} type="password"
placeholder={$t("placeholder_first_name")} label={$t("password_repeat")}
/> placeholder={$t("placeholder.password")}
<InputField bind:value={password2}
name="last_name" otherPasswordValue={password}
label={$t("last_name")} />
value={user.last_name} <InputField
placeholder={$t("placeholder_last_name")} name="first_name"
/> label={$t("first_name")}
<InputField bind:value={user.first_name}
name="email" placeholder={$t("placeholder.first_name")}
type="email" required={true}
label={$t("email")} />
value={user.email} <InputField
placeholder={$t("placeholder_email")} name="last_name"
/> label={$t("last_name")}
<InputField bind:value={user.last_name}
name="phone" placeholder={$t("placeholder.last_name")}
type="tel" required={true}
label={$t("phone")} />
value={user.phone || ""} <InputField
placeholder={$t("placeholder_phone")} name="company"
/> label={$t("company")}
<InputField bind:value={user.company}
name="birth_date" placeholder={$t("placeholder.company")}
type="date" />
label={$t("birth_date")} <InputField
value={user.birth_date || ""} name="email"
placeholder={$t("placeholder_birth_date")} type="email"
/> label={$t("email")}
<InputField bind:value={user.email}
name="address" placeholder={$t("placeholder.email")}
label={$t("address")} required={true}
value={user.address || ""} />
placeholder={$t("placeholder_address")} <InputField
/> name="phone"
<InputField type="tel"
name="zip_code" label={$t("phone")}
label={$t("zip_code")} bind:value={user.phone}
value={user.zip_code || ""} placeholder={$t("placeholder.phone")}
placeholder={$t("placeholder_zip_code")} />
/> <InputField
<InputField name="birth_date"
name="city" type="date"
label={$t("city")} label={$t("birth_date")}
value={user.city || ""} bind:value={user.date_of_birth}
placeholder={$t("placeholder_city")} placeholder={$t("placeholder.birth_date")}
/> />
</div> <InputField
{:else if activeTab == "membership"} name="address"
<div class="tab-content"> label={$t("address")}
<InputField bind:value={user.address}
name="membership_status" placeholder={$t("placeholder.address")}
label={$t("status")} />
value={user.membership?.status || ""} <InputField
/> name="zip_code"
<InputField label={$t("zip_code")}
name="membership_start_date" bind:value={user.zip_code}
type="date" placeholder={$t("placeholder.zip_code")}
label={$t("start")} />
value={user.membership?.start_date || ""} <InputField
placeholder={$t("placeholder_start_date")} name="city"
/> label={$t("city")}
<InputField bind:value={user.city}
name="membership_end_date" placeholder={$t("placeholder.city")}
type="date" />
label={$t("end")} <InputField
value={user.membership?.end_date || ""} name="notes"
placeholder={$t("placeholder_end_date")} type="textarea"
/> label={$t("notes")}
<InputField bind:value={user.notes}
name="parent_member_id" placeholder={$t("placeholder.notes", {
type="number" values: { name: user.first_name || "" },
label={$t("parent_member_id")} })}
value={user.membership?.parent_member_id || ""} rows={10}
placeholder={$t("placeholder_parent_member_id")} />
/> </div>
</div> <div
{:else if activeTab == "bankaccount"} class="tab-content"
<div class="tab-content"> style="display: {activeTab === 'licence' ? 'block' : 'none'}"
<InputField >
name="account_holder_name" <InputField
label={$t("bank_account_holder")} name="licence_status"
value={user.bank_account?.account_holder_name || ""} type="select"
placeholder={$t("placeholder_bank_account_holder")} label={$t("status")}
/> bind:value={user.drivers_licence.status}
<InputField options={licenceStatusOptions}
name="bank" />
label={$t("bank_name")} </div>
value={user.bank_account?.bank || ""} <div
placeholder={$t("placeholder_bank_name")} class="tab-content"
/> style="display: {activeTab === 'membership' ? 'block' : 'none'}"
<InputField >
name="iban" <InputField
label={$t("iban")} name="membership_status"
value={user.bank_account?.iban || ""} type="select"
placeholder={$t("placeholder_iban")} label={$t("status")}
toUpperCase={true} bind:value={user.membership.status}
/> options={membershipStatusOptions}
<InputField />
name="bic" <InputField
label={$t("bic")} name="subscription_model_name"
value={user.bank_account?.bic || ""} type="select"
placeholder={$t("placeholder_bic")} label={$t("subscription_model")}
toUpperCase={true} bind:value={user.membership.subscription_model.name}
/> options={subscriptionModelOptions}
<InputField />
name="mandate_reference" <div class="subscription-info">
label={$t("mandate_reference")} <div class="subscription-column">
value={user.bank_account?.mandate_reference || ""} <p>
placeholder={$t("placeholder_mandate_reference")} <strong>{$t("monthly_fee")}:</strong>
/> {selectedSubscriptionModel?.monthly_fee || "-"}
</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> </p>
{/each} <p>
<strong>{$t("hourly_rate")}:</strong>
{selectedSubscriptionModel?.hourly_rate || "-"}
</p>
{#if selectedSubscriptionModel?.included_hours_per_year}
<p>
<strong>{$t("included_hours_per_year")}:</strong>
{selectedSubscriptionModel?.included_hours_per_year}
</p>
{/if}
{#if selectedSubscriptionModel?.included_hours_per_month}
<p>
<strong>{$t("included_hours_per_month")}:</strong>
{selectedSubscriptionModel?.included_hours_per_month}
</p>
{/if}
</div>
<div class="subscription-column">
<p>
<strong>{$t("details")}:</strong>
{selectedSubscriptionModel?.details || "-"}
</p>
{#if selectedSubscriptionModel?.conditions}
<p>
<strong>{$t("conditions")}:</strong>
{selectedSubscriptionModel?.conditions}
</p>
{/if}
</div>
</div> </div>
{/if} <InputField
name="membership_start_date"
type="date"
label={$t("start")}
bind:value={user.membership.start_date}
placeholder={$t("placeholder.start_date")}
/>
<InputField
name="membership_end_date"
type="date"
label={$t("end")}
bind:value={user.membership.end_date}
placeholder={$t("placeholder.end_date")}
/>
<InputField
name="parent_member_id"
type="number"
label={$t("parent_member_id")}
bind:value={user.membership.parent_member_id}
placeholder={$t("placeholder.parent_member_id")}
/>
</div>
<div
class="tab-content"
style="display: {activeTab === 'bankaccount' ? 'block' : 'none'}"
>
<InputField
name="account_holder_name"
label={$t("bank_account_holder")}
bind:value={user.bank_account.account_holder_name}
placeholder={$t("placeholder.bank_account_holder")}
/>
<InputField
name="bank"
label={$t("bank_name")}
bind:value={user.bank_account.bank}
placeholder={$t("placeholder.bank_name")}
/>
<InputField
name="iban"
label={$t("iban")}
bind:value={user.bank_account.iban}
placeholder={$t("placeholder.iban")}
toUpperCase={true}
/>
<InputField
name="bic"
label={$t("bic")}
bind:value={user.bank_account.bic}
placeholder={$t("placeholder.bic")}
toUpperCase={true}
/>
<InputField
name="mandate_reference"
label={$t("mandate_reference")}
bind:value={user.bank_account.mandate_reference}
placeholder={$t("placeholder.mandate_reference")}
/>
</div>
<div class="button-container"> <div class="button-container">
{#if isUpdating} {#if isUpdating}
<SmallLoader width={30} message={"Aktualisiere..."} /> <SmallLoader width={30} message={"Aktualisiere..."} />
@@ -457,18 +566,26 @@
{/if} {/if}
<style> <style>
.error-container { .subscription-info {
background-color: #fee2e2; display: flex;
border: 1px solid #f87171; flex-wrap: wrap;
border-radius: 4px; gap: 1rem;
padding: 1rem; margin-top: 1rem;
margin-bottom: 1rem; font-size: 0.9rem;
} }
.error-message { .subscription-column {
color: #dc2626; flex: 1;
min-width: 200px;
}
.subscription-column p {
margin: 0.5rem 0; margin: 0.5rem 0;
font-size: 0.9rem; }
.subscription-column strong {
display: inline-block;
min-width: 100px;
} }
.tab-content { .tab-content {
padding: 1rem; padding: 1rem;