Compare commits
10 Commits
dbf7aca078
...
4bd10d24d2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4bd10d24d2 | ||
|
|
aea61e9440 | ||
|
|
5f1cb4e84f | ||
|
|
e56b58bd5f | ||
|
|
f0ed8e3446 | ||
|
|
27e1048d41 | ||
|
|
797aca3bb1 | ||
|
|
f7c1ad2b8e | ||
|
|
8e9eb22cb0 | ||
|
|
db84918cb1 |
90
frontend/src/app.d.ts
vendored
90
frontend/src/app.d.ts
vendored
@@ -1,53 +1,70 @@
|
|||||||
// See https://kit.svelte.dev/docs/types#app
|
// See https://kit.svelte.dev/docs/types#app
|
||||||
|
|
||||||
interface Subscription {
|
interface Subscription {
|
||||||
id: string;
|
id: number | -1;
|
||||||
name: string;
|
name: string | "";
|
||||||
details: string;
|
details?: string | "";
|
||||||
conditions: string | null;
|
conditions?: string | "";
|
||||||
monthly_fee: number;
|
monthly_fee?: number | -1;
|
||||||
hourly_rate: number;
|
hourly_rate?: number | -1;
|
||||||
included_hours_per_year: number | null;
|
included_hours_per_year?: number | 0;
|
||||||
included_hours_per_month: number | null;
|
included_hours_per_month?: number | 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Membership {
|
interface Membership {
|
||||||
id: string;
|
id: number | -1;
|
||||||
status: string;
|
status: number | -1;
|
||||||
start_date: string;
|
start_date: string | "";
|
||||||
end_date: string;
|
end_date: string | "";
|
||||||
parent_member_id: number | null;
|
parent_member_id: number | -1;
|
||||||
subscription_model: Subscription | null;
|
subscription_model: Subscription;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface BankAccount {
|
interface BankAccount {
|
||||||
id: string;
|
id: number | -1;
|
||||||
mandate_date_signed: string;
|
mandate_date_signed: string | "";
|
||||||
bank: string;
|
bank: string | "";
|
||||||
account_holder_name: string;
|
account_holder_name: string | "";
|
||||||
iban: string;
|
iban: string | "";
|
||||||
bic: string;
|
bic: string | "";
|
||||||
mandate_reference: string;
|
mandate_reference: string | "";
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DriversLicence {
|
||||||
|
id: number | -1;
|
||||||
|
status: number | -1;
|
||||||
|
licence_number: string | "";
|
||||||
|
issued_date: string | "";
|
||||||
|
expiration_date: string | "";
|
||||||
|
issuing_country: string | "";
|
||||||
|
licence_categories: LicenceCategory[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LicenceCategory {
|
||||||
|
id: number | -1;
|
||||||
|
category: string | "";
|
||||||
}
|
}
|
||||||
|
|
||||||
interface User {
|
interface User {
|
||||||
email: string;
|
email: string | "";
|
||||||
first_name: string;
|
first_name: string | "";
|
||||||
last_name: string;
|
last_name: string | "";
|
||||||
phone: string | null;
|
phone: string | "";
|
||||||
notes: string;
|
notes: string | "";
|
||||||
address: string;
|
address: string | "";
|
||||||
zip_code: string;
|
zip_code: string | "";
|
||||||
city: string;
|
city: string | "";
|
||||||
status: number;
|
status: number | -1;
|
||||||
id: string;
|
id: number | -1;
|
||||||
role_id: number;
|
role_id: number | -1;
|
||||||
date_of_birth: string;
|
date_of_birth: string | "";
|
||||||
|
company: string | "";
|
||||||
|
profile_picture: string | "";
|
||||||
|
payment_status: number | -1;
|
||||||
membership: Membership;
|
membership: Membership;
|
||||||
bank_account: BankAccount;
|
bank_account: BankAccount;
|
||||||
company: string | null;
|
drivers_licence: DriversLicence;
|
||||||
profile_picture: string | null;
|
notes: string | "";
|
||||||
payment_status: number;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
@@ -55,6 +72,7 @@ declare global {
|
|||||||
// interface Error {}
|
// interface Error {}
|
||||||
interface Locals {
|
interface Locals {
|
||||||
user: User;
|
user: User;
|
||||||
|
subscriptions: Subscription[];
|
||||||
}
|
}
|
||||||
// interface PageData {}
|
// interface PageData {}
|
||||||
// interface Platform {}
|
// interface Platform {}
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export async function handle({ event, resolve }) {
|
|||||||
}
|
}
|
||||||
// find the user based on the jwt
|
// find the user based on the jwt
|
||||||
|
|
||||||
const userData = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
// Check if the server sent a new token
|
// Check if the server sent a new token
|
||||||
const newToken = response.headers.get("Set-Cookie");
|
const newToken = response.headers.get("Set-Cookie");
|
||||||
@@ -44,7 +44,8 @@ export async function handle({ event, resolve }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
event.locals.user = userData;
|
event.locals.subscriptions = data.subscriptions;
|
||||||
|
event.locals.user = data.user;
|
||||||
if (event.locals.user.date_of_birth) {
|
if (event.locals.user.date_of_birth) {
|
||||||
event.locals.user.date_of_birth =
|
event.locals.user.date_of_birth =
|
||||||
event.locals.user.date_of_birth.split("T")[0];
|
event.locals.user.date_of_birth.split("T")[0];
|
||||||
|
|||||||
@@ -46,8 +46,8 @@
|
|||||||
<div class="header-nav-item">
|
<div class="header-nav-item">
|
||||||
<a href="/auth/about/{$page.data.user.id}">
|
<a href="/auth/about/{$page.data.user.id}">
|
||||||
<img
|
<img
|
||||||
src={$page.data.user.thumbnail
|
src={$page.data.user.profile_picture
|
||||||
? $page.data.user.thumbnail
|
? $page.data.user.profile_picture
|
||||||
: Avatar}
|
: Avatar}
|
||||||
alt={`${$page.data.user.first_name} ${$page.data.user.last_name}`}
|
alt={`${$page.data.user.first_name} ${$page.data.user.last_name}`}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -8,12 +8,21 @@
|
|||||||
/** @type {string} */
|
/** @type {string} */
|
||||||
export let type = "text";
|
export let type = "text";
|
||||||
|
|
||||||
/** @type {string} */
|
/** @type {string|Number|null} */
|
||||||
export let value = "";
|
export let value;
|
||||||
|
|
||||||
/** @type {string} */
|
/** @type {string} */
|
||||||
export let placeholder = "";
|
export let placeholder = "";
|
||||||
|
|
||||||
|
/** @type {Number} */
|
||||||
|
export let rows = 4;
|
||||||
|
|
||||||
|
/** @type {Array<{value: string | number, label: string, color?:string}>} */
|
||||||
|
export let options = [];
|
||||||
|
|
||||||
|
/** @type {Boolean} */
|
||||||
|
export let required = false;
|
||||||
|
|
||||||
/** @type {string} */
|
/** @type {string} */
|
||||||
export let label = "";
|
export let label = "";
|
||||||
|
|
||||||
@@ -23,8 +32,6 @@
|
|||||||
/** @type {boolean} */
|
/** @type {boolean} */
|
||||||
export let toUpperCase = false;
|
export let toUpperCase = false;
|
||||||
|
|
||||||
const dispatch = createEventDispatcher();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Event} event - The input event
|
* @param {Event} event - The input event
|
||||||
*/
|
*/
|
||||||
@@ -38,60 +45,68 @@
|
|||||||
target.value = inputValue; // Update the input field value
|
target.value = inputValue; // Update the input field value
|
||||||
}
|
}
|
||||||
value = inputValue;
|
value = inputValue;
|
||||||
// dispatch("input", { name, value: inputValue });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates the field
|
* Validates the field
|
||||||
* @param {string} name - The name of the field
|
* @param {string} name - The name of the field
|
||||||
* @param {string} value - The value of the field
|
* @param {string|Number|null} value - The value of the field
|
||||||
|
* @param {Boolean} required - The requirements of the field
|
||||||
* @returns {string|null} The error message or null if valid
|
* @returns {string|null} The error message or null if valid
|
||||||
*/
|
*/
|
||||||
function validateField(name, value) {
|
function validateField(name, value, required) {
|
||||||
|
if (
|
||||||
|
value === null ||
|
||||||
|
(typeof value === "string" && !value.trim() && !required)
|
||||||
|
)
|
||||||
|
return null;
|
||||||
switch (name) {
|
switch (name) {
|
||||||
case "first_name":
|
|
||||||
case "last_name":
|
|
||||||
case "address":
|
|
||||||
case "city":
|
|
||||||
case "account_holder_name":
|
|
||||||
case "bank":
|
|
||||||
return value.trim() ? null : $t("required");
|
|
||||||
case "birth_date":
|
|
||||||
case "membership_start_date":
|
case "membership_start_date":
|
||||||
return value.trim() ? null : $t("required_date");
|
return typeof value === "string" && value.trim()
|
||||||
|
? null
|
||||||
|
: $t("validation.date");
|
||||||
case "email":
|
case "email":
|
||||||
return /^\S+@\S+\.\S+$/.test(value) ? null : $t("invalid_email");
|
return typeof value === "string" && /^\S+@\S+\.\S+$/.test(value)
|
||||||
|
? null
|
||||||
|
: $t("validation.email");
|
||||||
case "password":
|
case "password":
|
||||||
case "password2":
|
case "password2":
|
||||||
if (!value.trim()) {
|
if (typeof value === "string" && value.length < 8) {
|
||||||
return null;
|
return $t("validation.password");
|
||||||
}
|
|
||||||
if (value.length < 8) {
|
|
||||||
return $t("required_password");
|
|
||||||
}
|
}
|
||||||
if (otherPasswordValue && value !== otherPasswordValue) {
|
if (otherPasswordValue && value !== otherPasswordValue) {
|
||||||
return $t("required_password_match");
|
return $t("validation.password_match");
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
case "phone":
|
case "phone":
|
||||||
return /^\+?[0-9\s()-]{7,}$/.test(value) ? null : $t("invalid_phone");
|
return typeof value === "string" && /^\+?[0-9\s()-]{7,}$/.test(value)
|
||||||
|
? null
|
||||||
|
: $t("validation.phone");
|
||||||
case "zip_code":
|
case "zip_code":
|
||||||
return /^\d{5}$/.test(value) ? null : $t("invalid_zip_code");
|
return typeof value === "string" && /^\d{5}$/.test(value)
|
||||||
|
? null
|
||||||
|
: $t("validation.zip_code");
|
||||||
case "iban":
|
case "iban":
|
||||||
return /^[A-Z]{2}\d{2}[A-Z0-9]{1,30}$/.test(value)
|
return typeof value === "string" &&
|
||||||
|
/^[A-Z]{2}\d{2}[A-Z0-9]{1,30}$/.test(value)
|
||||||
? null
|
? null
|
||||||
: $t("invalid_iban");
|
: $t("validation.iban");
|
||||||
case "bic":
|
case "bic":
|
||||||
return /^[A-Z]{6}[A-Z2-9][A-NP-Z0-9]([A-Z0-9]{3})?$/.test(value)
|
return typeof value === "string" &&
|
||||||
|
/^[A-Z]{6}[A-Z2-9][A-NP-Z0-9]([A-Z0-9]{3})?$/.test(value)
|
||||||
? null
|
? null
|
||||||
: $t("invalid_bic");
|
: $t("validation.bic");
|
||||||
default:
|
default:
|
||||||
return null;
|
return typeof value === "string" && !value.trim() && required
|
||||||
|
? $t("validation.required")
|
||||||
|
: null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$: error = validateField(name, value);
|
$: error = validateField(name, value, required);
|
||||||
|
$: selectedOption = options.find((option) => option.value == value);
|
||||||
|
$: selectedColor = selectedOption ? selectedOption.color : "";
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="input-box">
|
<div class="input-box">
|
||||||
@@ -100,22 +115,49 @@
|
|||||||
{#if error}
|
{#if error}
|
||||||
<span class="error-message">{error}</span>
|
<span class="error-message">{error}</span>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if type === "select"}
|
||||||
|
<select
|
||||||
|
{name}
|
||||||
|
bind:value
|
||||||
|
{required}
|
||||||
|
class="input select"
|
||||||
|
style={selectedColor ? `color: ${selectedColor};` : ""}
|
||||||
|
>
|
||||||
|
{#each options as option}
|
||||||
|
<option value={option.value}>{option.label}</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
{:else if type === "textarea"}
|
||||||
|
<textarea
|
||||||
|
{name}
|
||||||
|
{placeholder}
|
||||||
|
{required}
|
||||||
|
{value}
|
||||||
|
{rows}
|
||||||
|
class="input textarea"
|
||||||
|
style="height:{rows * 1.5}em;"
|
||||||
|
/>
|
||||||
|
{:else}
|
||||||
<input
|
<input
|
||||||
{name}
|
{name}
|
||||||
{type}
|
{type}
|
||||||
{placeholder}
|
{placeholder}
|
||||||
{value}
|
{value}
|
||||||
|
{required}
|
||||||
on:input={handleInput}
|
on:input={handleInput}
|
||||||
on:blur={handleInput}
|
on:blur={handleInput}
|
||||||
class="input"
|
class="input"
|
||||||
/>
|
/>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
.select {
|
||||||
|
padding-right: 1.5em;
|
||||||
|
}
|
||||||
.input-error-container {
|
.input-error-container {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-end;
|
align-items: flex-end;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
max-width: 444px;
|
max-width: 444px;
|
||||||
@@ -131,4 +173,17 @@
|
|||||||
.input {
|
.input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
|
input,
|
||||||
|
textarea,
|
||||||
|
select {
|
||||||
|
width: 100%;
|
||||||
|
padding: 0.5rem;
|
||||||
|
border: 1px solid #ccc;
|
||||||
|
border-radius: 4px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
textarea {
|
||||||
|
resize: vertical;
|
||||||
|
min-height: 100px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -347,9 +347,9 @@ li strong {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
padding: 0 20px;
|
padding: 10px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 73px;
|
height: auto;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
background-color: #2f2f2f;
|
background-color: #2f2f2f;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
|
|||||||
6
frontend/src/lib/css/styles.min.css
vendored
6
frontend/src/lib/css/styles.min.css
vendored
@@ -347,9 +347,9 @@ li strong {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: space-between;
|
justify-content: space-between;
|
||||||
margin: 10px 0;
|
margin: 10px 0;
|
||||||
padding: 0 20px;
|
padding: 10px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 73px;
|
height: auto;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
background-color: #2f2f2f;
|
background-color: #2f2f2f;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
@@ -371,7 +371,7 @@ li strong {
|
|||||||
}
|
}
|
||||||
@media (min-width: 680px) {
|
@media (min-width: 680px) {
|
||||||
.input-box {
|
.input-box {
|
||||||
padding: 0 30px;
|
padding: 10px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.btn-container {
|
.btn-container {
|
||||||
|
|||||||
@@ -12,6 +12,60 @@ export default {
|
|||||||
4: "Bearbeiter",
|
4: "Bearbeiter",
|
||||||
8: "Administrator",
|
8: "Administrator",
|
||||||
},
|
},
|
||||||
|
placeholder: {
|
||||||
|
password: "Passwort eingeben...",
|
||||||
|
email: "Emailadresse eingeben...",
|
||||||
|
company: "Firmennamen eingeben...",
|
||||||
|
first_name: "Vornamen eingeben...",
|
||||||
|
last_name: "Nachnamen eingeben...",
|
||||||
|
phone: "Telefonnummer eingeben...",
|
||||||
|
address: "Straße und Hausnummer eingeben...",
|
||||||
|
zip_code: "Postleitzahl eingeben...",
|
||||||
|
city: "Wohnort eingeben...",
|
||||||
|
bank_name: "Namen der Bank eingeben...",
|
||||||
|
parent_member_id: "Mitgliedsnr des Hauptmitglieds eingeben...",
|
||||||
|
bank_account_holder: "Namen eingeben...",
|
||||||
|
iban: "IBAN eingeben..",
|
||||||
|
bic: "BIC eingeben(Bei nicht deutschen Konten)...",
|
||||||
|
mandate_reference: "SEPA Mandatsreferenz eingeben..",
|
||||||
|
notes: "Deine Notizen zu {name}...",
|
||||||
|
},
|
||||||
|
validation: {
|
||||||
|
required: "Eingabe benötigt",
|
||||||
|
password: "Password zu kurz, mindestens 8 Zeichen",
|
||||||
|
password_match: "Passwörter stimmen nicht überein!",
|
||||||
|
phone: "Ungültiges Format(+491738762387 oder 0173850698)",
|
||||||
|
zip_code: "Ungültige Postleitzahl(Nur deutsche Wohnorte sind zulässig)",
|
||||||
|
bic: "Ungültige BIC",
|
||||||
|
iban: "Ungültige IBAN",
|
||||||
|
date: "Bitte geben Sie ein Datum ein",
|
||||||
|
email: "Ungültige Emailadresse",
|
||||||
|
},
|
||||||
|
licenceCategory: {
|
||||||
|
AM: "Mopeds und leichte vierrädrige Kraftfahrzeuge(50ccm,max 45km/h)",
|
||||||
|
A1: "Leichte Motorräder(125ccm)",
|
||||||
|
A2: "Motorräder mit mittlerer Leistung(max 35kW)",
|
||||||
|
A: "Motorräder",
|
||||||
|
B: "Kraftfahrzeuge ≤ 3500 kg, ≤ 8 Sitzplätze",
|
||||||
|
C1: "Mittelschwere Fahrzeuge -7500 kg",
|
||||||
|
C: "Schwere Nutzfahrzeuge > 3500 kg",
|
||||||
|
D1: "Kleinbusse 9-16 Sitzplätze",
|
||||||
|
D: "Busse > 8 Sitzplätze",
|
||||||
|
BE: "Fahrzeugklasse B mit Anhänger",
|
||||||
|
C1E: "Fahrzeugklasse C1 mit Anhänger",
|
||||||
|
CE: "Fahrzeugklasse C mit Anhänger",
|
||||||
|
D1E: "Fahrzeugklasse D1 mit Anhänger",
|
||||||
|
DE: "Fahrzeugklasse D mit Anhänger",
|
||||||
|
L: "Land-, Forstwirtschaftsfahrzeuge, Stapler max 40km/h",
|
||||||
|
T: "Land-, Forstwirtschaftsfahrzeuge, Stapler max 60km/h",
|
||||||
|
},
|
||||||
|
subscription_model: "Mitgliedschatfsmodell",
|
||||||
|
licence: "Lizenz",
|
||||||
|
monthly_fee: "Monatliche Gebühr",
|
||||||
|
hourly_rate: "Stundensatz",
|
||||||
|
details: "Details",
|
||||||
|
conditions: "Bedingungen",
|
||||||
|
user_role: "Nutzerrolle",
|
||||||
unknown: "Unbekannt",
|
unknown: "Unbekannt",
|
||||||
notes: "Notizen",
|
notes: "Notizen",
|
||||||
address: "Straße & Hausnummer",
|
address: "Straße & Hausnummer",
|
||||||
@@ -20,9 +74,8 @@ export default {
|
|||||||
forgot_password: "Passwort vergessen?",
|
forgot_password: "Passwort vergessen?",
|
||||||
password: "Passwort",
|
password: "Passwort",
|
||||||
password_repeat: "Passwort wiederholen",
|
password_repeat: "Passwort wiederholen",
|
||||||
placeholder_password: "Passwort eingeben...",
|
|
||||||
email: "Email",
|
email: "Email",
|
||||||
placeholder_email: "Emailadresse eingeben...",
|
company: "Firma",
|
||||||
login: "Anmelden",
|
login: "Anmelden",
|
||||||
user: "Nutzer",
|
user: "Nutzer",
|
||||||
user_login: "Nutzer Anmeldung",
|
user_login: "Nutzer Anmeldung",
|
||||||
@@ -34,35 +87,13 @@ export default {
|
|||||||
last_name: "Nachname",
|
last_name: "Nachname",
|
||||||
phone: "Telefonnummer",
|
phone: "Telefonnummer",
|
||||||
birth_date: "Geburtstag",
|
birth_date: "Geburtstag",
|
||||||
placeholder_first_name: "Vornamen eingeben...",
|
|
||||||
placeholder_last_name: "Nachnamen eingeben...",
|
|
||||||
invalid_email: "Ungültige Emailadresse",
|
|
||||||
placeholder_phone: "Telefonnummer eingeben...",
|
|
||||||
placeholder_address: "Straße und Hausnummer eingeben...",
|
|
||||||
placeholder_zip_code: "Postleitzahl eingeben...",
|
|
||||||
placeholder_city: "Wohnort eingeben...",
|
|
||||||
placeholder_bank_name: "Namen der Bank eingeben...",
|
|
||||||
status: "Status",
|
status: "Status",
|
||||||
start: "Beginn",
|
start: "Beginn",
|
||||||
end: "Ende",
|
end: "Ende",
|
||||||
parent_member_id: "Hauptmitgliedsnr.",
|
parent_member_id: "Hauptmitgliedsnr.",
|
||||||
placeholder_parent_member_id: "Mitgliedsnr des Hauptmitglieds eingeben...",
|
|
||||||
bank_account_holder: "Kontoinhaber",
|
bank_account_holder: "Kontoinhaber",
|
||||||
bank_name: "Bank",
|
bank_name: "Bank",
|
||||||
iban: "IBAN",
|
iban: "IBAN",
|
||||||
bic: "BIC",
|
bic: "BIC",
|
||||||
mandate_reference: "SEPA Mandat",
|
mandate_reference: "SEPA Mandat",
|
||||||
placeholder_bank_account_holder: "Namen eingeben...",
|
|
||||||
placeholder_iban: "IBAN eingeben..",
|
|
||||||
placeholder_bic: "BIC eingeben(Bei nicht deutschen Konten)...",
|
|
||||||
placeholder_mandate_reference: "SEPA Mandatsreferenz eingeben..",
|
|
||||||
required: "Eingabe benötigt",
|
|
||||||
required_password: "Password zu kurz, mindestens 8 Zeichen",
|
|
||||||
required_password_match: "Passwörter stimmen nicht überein!",
|
|
||||||
invalid_phone: "Ungültiges Format(+491738762387 oder 0173850698)",
|
|
||||||
invalid_zip_code:
|
|
||||||
"Ungültige Postleitzahl(Nur deutsche Wohnorte sind zulässig)",
|
|
||||||
invalid_bic: "Ungültige BIC",
|
|
||||||
invalid_iban: "Ungültige IBAN",
|
|
||||||
required_date: "Bitte geben Sie ein Datum ein",
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -68,13 +68,20 @@ export function isEmpty(obj) {
|
|||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test whether or not an object is empty.
|
* @typedef {Object} FormattedError
|
||||||
|
* @property {string} error - The error message
|
||||||
|
* @property {number} id - A unique identifier for the error
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Format Error object(s)
|
||||||
* @param {any} obj - The object to test
|
* @param {any} obj - The object to test
|
||||||
* @returns `true` or `false`
|
* @returns @type {FormattedError[]}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export function formatError(obj) {
|
export function formatError(obj) {
|
||||||
|
/** @type {FormattedError[]} */
|
||||||
const errors = [];
|
const errors = [];
|
||||||
if (typeof obj === "object" && obj !== null) {
|
if (typeof obj === "object" && obj !== null) {
|
||||||
if (Array.isArray(obj)) {
|
if (Array.isArray(obj)) {
|
||||||
@@ -103,3 +110,9 @@ export function formatError(obj) {
|
|||||||
|
|
||||||
return errors;
|
return errors;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function toRFC3339(dateString) {
|
||||||
|
if (!dateString) return "";
|
||||||
|
const date = new Date(dateString);
|
||||||
|
return date.toISOString();
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/** @type {import('./$types').LayoutLoad} */
|
/** @type {import('./$types').LayoutLoad} */
|
||||||
export async function load({ fetch, url, data }) {
|
export async function load({ fetch, url, data }) {
|
||||||
const { user } = data;
|
const { user, subscriptions } = data;
|
||||||
return { fetch, url: url.pathname, user };
|
return { fetch, url: url.pathname, user, subscriptions };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,5 +2,6 @@
|
|||||||
export async function load({ locals }) {
|
export async function load({ locals }) {
|
||||||
return {
|
return {
|
||||||
user: locals.user,
|
user: locals.user,
|
||||||
|
subscriptions: locals.subscriptions,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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}`);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
@@ -99,9 +132,10 @@ export const actions = {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
thumbnail: response["s3_url"],
|
profile_picture: response[""],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param request - The request object
|
* @param request - The request object
|
||||||
@@ -132,7 +166,7 @@ export const actions = {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
success: true,
|
success: true,
|
||||||
thumbnail: "",
|
profile_picture: "",
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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);
|
||||||
};
|
};
|
||||||
@@ -108,7 +128,7 @@
|
|||||||
/** @type {any} */
|
/** @type {any} */
|
||||||
const res = result;
|
const res = result;
|
||||||
if (result.type === "success" || result.type === "redirect") {
|
if (result.type === "success" || result.type === "redirect") {
|
||||||
user.thumbnail = res.data.thumbnail;
|
user.profile_picture = res.data.profile_picture;
|
||||||
}
|
}
|
||||||
await applyAction(result);
|
await applyAction(result);
|
||||||
};
|
};
|
||||||
@@ -118,7 +138,7 @@
|
|||||||
<div class="hero-container">
|
<div class="hero-container">
|
||||||
<div class="hero-logo">
|
<div class="hero-logo">
|
||||||
<img
|
<img
|
||||||
src={user.thumbnail ? user.thumbnail : Avatar}
|
src={user.profile_picture ? user.profile_picture : Avatar}
|
||||||
alt={`${user.first_name} ${user.last_name}`}
|
alt={`${user.first_name} ${user.last_name}`}
|
||||||
width="200"
|
width="200"
|
||||||
/>
|
/>
|
||||||
@@ -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}
|
||||||
@@ -195,13 +215,13 @@
|
|||||||
>
|
>
|
||||||
<div class="current-avatar">
|
<div class="current-avatar">
|
||||||
<ImageInput
|
<ImageInput
|
||||||
avatar={user.thumbnail}
|
avatar={user.profile_picture}
|
||||||
fieldName="thumbnail"
|
fieldName="profile_picture"
|
||||||
title="Nutzerbild auswählen"
|
title="Nutzerbild auswählen"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="avatar-buttons">
|
<div class="avatar-buttons">
|
||||||
{#if !user.thumbnail}
|
{#if !user.profile_picture}
|
||||||
{#if isUploading}
|
{#if isUploading}
|
||||||
<SmallLoader width={30} message={"Uploading..."} />
|
<SmallLoader width={30} message={"Uploading..."} />
|
||||||
{:else}
|
{:else}
|
||||||
@@ -211,8 +231,8 @@
|
|||||||
<input
|
<input
|
||||||
type="hidden"
|
type="hidden"
|
||||||
hidden
|
hidden
|
||||||
name="thumbnail_url"
|
name="profile_picture_url"
|
||||||
value={user.thumbnail}
|
bind:value={user.profile_picture}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
{#if isUploading}
|
{#if isUploading}
|
||||||
@@ -240,7 +260,7 @@
|
|||||||
<button
|
<button
|
||||||
class="avatar-option"
|
class="avatar-option"
|
||||||
on:click={() => {
|
on:click={() => {
|
||||||
user.thumbnail = avatar.src;
|
user.profile_picture = avatar.src;
|
||||||
showAvatars = false;
|
showAvatars = false;
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
@@ -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
|
||||||
@@ -279,7 +300,12 @@
|
|||||||
{/each}
|
{/each}
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<input type="hidden" hidden name="thumbnail" value={user.thumbnail} />
|
<input
|
||||||
|
type="hidden"
|
||||||
|
hidden
|
||||||
|
name="profile_picture"
|
||||||
|
bind:value={user.profile_picture}
|
||||||
|
/>
|
||||||
|
|
||||||
<div class="button-container">
|
<div class="button-container">
|
||||||
{#each TABS as tab}
|
{#each TABS as tab}
|
||||||
@@ -293,13 +319,31 @@
|
|||||||
</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
|
||||||
|
name="role_id"
|
||||||
|
type="select"
|
||||||
|
label={$t("user_role")}
|
||||||
|
bind:value={user.role_id}
|
||||||
|
options={userRoleOptions}
|
||||||
|
/>
|
||||||
|
{/if}
|
||||||
<InputField
|
<InputField
|
||||||
name="password"
|
name="password"
|
||||||
type="password"
|
type="password"
|
||||||
label={$t("password")}
|
label={$t("password")}
|
||||||
placeholder={$t("placeholder_password")}
|
placeholder={$t("placeholder.password")}
|
||||||
bind:value={password}
|
bind:value={password}
|
||||||
otherPasswordValue={password2}
|
otherPasswordValue={password2}
|
||||||
/>
|
/>
|
||||||
@@ -307,136 +351,206 @@
|
|||||||
name="password2"
|
name="password2"
|
||||||
type="password"
|
type="password"
|
||||||
label={$t("password_repeat")}
|
label={$t("password_repeat")}
|
||||||
placeholder={$t("placeholder_password")}
|
placeholder={$t("placeholder.password")}
|
||||||
bind:value={password2}
|
bind:value={password2}
|
||||||
otherPasswordValue={password}
|
otherPasswordValue={password}
|
||||||
/>
|
/>
|
||||||
<InputField
|
<InputField
|
||||||
name="first_name"
|
name="first_name"
|
||||||
label={$t("first_name")}
|
label={$t("first_name")}
|
||||||
value={user.first_name}
|
bind:value={user.first_name}
|
||||||
placeholder={$t("placeholder_first_name")}
|
placeholder={$t("placeholder.first_name")}
|
||||||
|
required={true}
|
||||||
/>
|
/>
|
||||||
<InputField
|
<InputField
|
||||||
name="last_name"
|
name="last_name"
|
||||||
label={$t("last_name")}
|
label={$t("last_name")}
|
||||||
value={user.last_name}
|
bind:value={user.last_name}
|
||||||
placeholder={$t("placeholder_last_name")}
|
placeholder={$t("placeholder.last_name")}
|
||||||
|
required={true}
|
||||||
|
/>
|
||||||
|
<InputField
|
||||||
|
name="company"
|
||||||
|
label={$t("company")}
|
||||||
|
bind:value={user.company}
|
||||||
|
placeholder={$t("placeholder.company")}
|
||||||
/>
|
/>
|
||||||
<InputField
|
<InputField
|
||||||
name="email"
|
name="email"
|
||||||
type="email"
|
type="email"
|
||||||
label={$t("email")}
|
label={$t("email")}
|
||||||
value={user.email}
|
bind:value={user.email}
|
||||||
placeholder={$t("placeholder_email")}
|
placeholder={$t("placeholder.email")}
|
||||||
|
required={true}
|
||||||
/>
|
/>
|
||||||
<InputField
|
<InputField
|
||||||
name="phone"
|
name="phone"
|
||||||
type="tel"
|
type="tel"
|
||||||
label={$t("phone")}
|
label={$t("phone")}
|
||||||
value={user.phone || ""}
|
bind:value={user.phone}
|
||||||
placeholder={$t("placeholder_phone")}
|
placeholder={$t("placeholder.phone")}
|
||||||
/>
|
/>
|
||||||
<InputField
|
<InputField
|
||||||
name="birth_date"
|
name="birth_date"
|
||||||
type="date"
|
type="date"
|
||||||
label={$t("birth_date")}
|
label={$t("birth_date")}
|
||||||
value={user.birth_date || ""}
|
bind:value={user.date_of_birth}
|
||||||
placeholder={$t("placeholder_birth_date")}
|
placeholder={$t("placeholder.birth_date")}
|
||||||
/>
|
/>
|
||||||
<InputField
|
<InputField
|
||||||
name="address"
|
name="address"
|
||||||
label={$t("address")}
|
label={$t("address")}
|
||||||
value={user.address || ""}
|
bind:value={user.address}
|
||||||
placeholder={$t("placeholder_address")}
|
placeholder={$t("placeholder.address")}
|
||||||
/>
|
/>
|
||||||
<InputField
|
<InputField
|
||||||
name="zip_code"
|
name="zip_code"
|
||||||
label={$t("zip_code")}
|
label={$t("zip_code")}
|
||||||
value={user.zip_code || ""}
|
bind:value={user.zip_code}
|
||||||
placeholder={$t("placeholder_zip_code")}
|
placeholder={$t("placeholder.zip_code")}
|
||||||
/>
|
/>
|
||||||
<InputField
|
<InputField
|
||||||
name="city"
|
name="city"
|
||||||
label={$t("city")}
|
label={$t("city")}
|
||||||
value={user.city || ""}
|
bind:value={user.city}
|
||||||
placeholder={$t("placeholder_city")}
|
placeholder={$t("placeholder.city")}
|
||||||
|
/>
|
||||||
|
<InputField
|
||||||
|
name="notes"
|
||||||
|
type="textarea"
|
||||||
|
label={$t("notes")}
|
||||||
|
bind:value={user.notes}
|
||||||
|
placeholder={$t("placeholder.notes", {
|
||||||
|
values: { name: user.first_name || "" },
|
||||||
|
})}
|
||||||
|
rows={10}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{:else if activeTab == "membership"}
|
<div
|
||||||
<div class="tab-content">
|
class="tab-content"
|
||||||
|
style="display: {activeTab === 'licence' ? 'block' : 'none'}"
|
||||||
|
>
|
||||||
|
<InputField
|
||||||
|
name="licence_status"
|
||||||
|
type="select"
|
||||||
|
label={$t("status")}
|
||||||
|
bind:value={user.drivers_licence.status}
|
||||||
|
options={licenceStatusOptions}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
class="tab-content"
|
||||||
|
style="display: {activeTab === 'membership' ? 'block' : 'none'}"
|
||||||
|
>
|
||||||
<InputField
|
<InputField
|
||||||
name="membership_status"
|
name="membership_status"
|
||||||
|
type="select"
|
||||||
label={$t("status")}
|
label={$t("status")}
|
||||||
value={user.membership?.status || ""}
|
bind:value={user.membership.status}
|
||||||
|
options={membershipStatusOptions}
|
||||||
/>
|
/>
|
||||||
|
<InputField
|
||||||
|
name="subscription_model_name"
|
||||||
|
type="select"
|
||||||
|
label={$t("subscription_model")}
|
||||||
|
bind:value={user.membership.subscription_model.name}
|
||||||
|
options={subscriptionModelOptions}
|
||||||
|
/>
|
||||||
|
<div class="subscription-info">
|
||||||
|
<div class="subscription-column">
|
||||||
|
<p>
|
||||||
|
<strong>{$t("monthly_fee")}:</strong>
|
||||||
|
{selectedSubscriptionModel?.monthly_fee || "-"}
|
||||||
|
</p>
|
||||||
|
<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>
|
||||||
<InputField
|
<InputField
|
||||||
name="membership_start_date"
|
name="membership_start_date"
|
||||||
type="date"
|
type="date"
|
||||||
label={$t("start")}
|
label={$t("start")}
|
||||||
value={user.membership?.start_date || ""}
|
bind:value={user.membership.start_date}
|
||||||
placeholder={$t("placeholder_start_date")}
|
placeholder={$t("placeholder.start_date")}
|
||||||
/>
|
/>
|
||||||
<InputField
|
<InputField
|
||||||
name="membership_end_date"
|
name="membership_end_date"
|
||||||
type="date"
|
type="date"
|
||||||
label={$t("end")}
|
label={$t("end")}
|
||||||
value={user.membership?.end_date || ""}
|
bind:value={user.membership.end_date}
|
||||||
placeholder={$t("placeholder_end_date")}
|
placeholder={$t("placeholder.end_date")}
|
||||||
/>
|
/>
|
||||||
<InputField
|
<InputField
|
||||||
name="parent_member_id"
|
name="parent_member_id"
|
||||||
type="number"
|
type="number"
|
||||||
label={$t("parent_member_id")}
|
label={$t("parent_member_id")}
|
||||||
value={user.membership?.parent_member_id || ""}
|
bind:value={user.membership.parent_member_id}
|
||||||
placeholder={$t("placeholder_parent_member_id")}
|
placeholder={$t("placeholder.parent_member_id")}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{:else if activeTab == "bankaccount"}
|
<div
|
||||||
<div class="tab-content">
|
class="tab-content"
|
||||||
|
style="display: {activeTab === 'bankaccount' ? 'block' : 'none'}"
|
||||||
|
>
|
||||||
<InputField
|
<InputField
|
||||||
name="account_holder_name"
|
name="account_holder_name"
|
||||||
label={$t("bank_account_holder")}
|
label={$t("bank_account_holder")}
|
||||||
value={user.bank_account?.account_holder_name || ""}
|
bind:value={user.bank_account.account_holder_name}
|
||||||
placeholder={$t("placeholder_bank_account_holder")}
|
placeholder={$t("placeholder.bank_account_holder")}
|
||||||
/>
|
/>
|
||||||
<InputField
|
<InputField
|
||||||
name="bank"
|
name="bank"
|
||||||
label={$t("bank_name")}
|
label={$t("bank_name")}
|
||||||
value={user.bank_account?.bank || ""}
|
bind:value={user.bank_account.bank}
|
||||||
placeholder={$t("placeholder_bank_name")}
|
placeholder={$t("placeholder.bank_name")}
|
||||||
/>
|
/>
|
||||||
<InputField
|
<InputField
|
||||||
name="iban"
|
name="iban"
|
||||||
label={$t("iban")}
|
label={$t("iban")}
|
||||||
value={user.bank_account?.iban || ""}
|
bind:value={user.bank_account.iban}
|
||||||
placeholder={$t("placeholder_iban")}
|
placeholder={$t("placeholder.iban")}
|
||||||
toUpperCase={true}
|
toUpperCase={true}
|
||||||
/>
|
/>
|
||||||
<InputField
|
<InputField
|
||||||
name="bic"
|
name="bic"
|
||||||
label={$t("bic")}
|
label={$t("bic")}
|
||||||
value={user.bank_account?.bic || ""}
|
bind:value={user.bank_account.bic}
|
||||||
placeholder={$t("placeholder_bic")}
|
placeholder={$t("placeholder.bic")}
|
||||||
toUpperCase={true}
|
toUpperCase={true}
|
||||||
/>
|
/>
|
||||||
<InputField
|
<InputField
|
||||||
name="mandate_reference"
|
name="mandate_reference"
|
||||||
label={$t("mandate_reference")}
|
label={$t("mandate_reference")}
|
||||||
value={user.bank_account?.mandate_reference || ""}
|
bind:value={user.bank_account.mandate_reference}
|
||||||
placeholder={$t("placeholder_mandate_reference")}
|
placeholder={$t("placeholder.mandate_reference")}
|
||||||
/>
|
/>
|
||||||
</div>
|
</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">
|
<div class="button-container">
|
||||||
{#if isUpdating}
|
{#if isUpdating}
|
||||||
<SmallLoader width={30} message={"Aktualisiere..."} />
|
<SmallLoader width={30} message={"Aktualisiere..."} />
|
||||||
@@ -452,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;
|
||||||
|
|||||||
@@ -55,7 +55,7 @@
|
|||||||
class="input"
|
class="input"
|
||||||
type="email"
|
type="email"
|
||||||
name="email"
|
name="email"
|
||||||
placeholder="{$t('placeholder_email')} "
|
placeholder="{$t('placeholder.email')} "
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div class="input-box">
|
<div class="input-box">
|
||||||
@@ -65,7 +65,7 @@
|
|||||||
class="input"
|
class="input"
|
||||||
type="password"
|
type="password"
|
||||||
name="password"
|
name="password"
|
||||||
placeholder={$t("placeholder_password")}
|
placeholder={$t("placeholder.password")}
|
||||||
/>
|
/>
|
||||||
<a href="/auth/password/request-change" class="forgot-password"
|
<a href="/auth/password/request-change" class="forgot-password"
|
||||||
>{$t("forgot_password")}?</a
|
>{$t("forgot_password")}?</a
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ export const actions = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const res = await fetch(
|
const res = await fetch(
|
||||||
`${BASE_API_URI}/users/backend/logout/`,
|
`${BASE_API_URI}/backend/users/logout/`,
|
||||||
requestInitOptions
|
requestInitOptions
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -34,7 +34,6 @@ export const actions = {
|
|||||||
return fail(400, { errors: errors });
|
return fail(400, { errors: errors });
|
||||||
}
|
}
|
||||||
|
|
||||||
// The server should clear the cookie, so we don't need to handle it here
|
|
||||||
// eat the cookie
|
// eat the cookie
|
||||||
cookies.delete("jwt", { path: "/" });
|
cookies.delete("jwt", { path: "/" });
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user