styling, DateOfBirth corrected

This commit is contained in:
Alex
2025-01-31 19:27:15 +01:00
parent c2d5188765
commit 67ef3a2fca
14 changed files with 716 additions and 779 deletions

126
frontend/src/app.d.ts vendored
View File

@@ -1,87 +1,87 @@
// See https://kit.svelte.dev/docs/types#app
interface Subscription {
id: number | -1;
name: string | "";
details?: string | "";
conditions?: string | "";
monthly_fee?: number | -1;
hourly_rate?: number | -1;
included_hours_per_year?: number | 0;
included_hours_per_month?: number | 0;
id: number | -1;
name: string | '';
details?: string | '';
conditions?: string | '';
monthly_fee?: number | -1;
hourly_rate?: number | -1;
included_hours_per_year?: number | 0;
included_hours_per_month?: number | 0;
}
interface Membership {
id: number | -1;
status: number | -1;
start_date: string | "";
end_date: string | "";
parent_member_id: number | -1;
subscription_model: Subscription;
id: number | -1;
status: number | -1;
start_date: string | '';
end_date: string | '';
parent_member_id: number | -1;
subscription_model: Subscription;
}
interface BankAccount {
id: number | -1;
mandate_date_signed: string | "";
bank: string | "";
account_holder_name: string | "";
iban: string | "";
bic: string | "";
mandate_reference: string | "";
id: number | -1;
mandate_date_signed: string | '';
bank: string | '';
account_holder_name: string | '';
iban: string | '';
bic: string | '';
mandate_reference: string | '';
}
interface Licence {
id: number | -1;
status: number | -1;
licence_number: string | "";
issued_date: string | "";
expiration_date: string | "";
country: string | "";
licence_categories: LicenceCategory[];
id: number | -1;
status: number | -1;
licence_number: string | '';
issued_date: string | '';
expiration_date: string | '';
country: string | '';
licence_categories: LicenceCategory[];
}
interface LicenceCategory {
id: number | -1;
category: string | "";
id: number | -1;
category: string | '';
}
interface User {
email: string | "";
first_name: string | "";
last_name: string | "";
phone: string | "";
notes: string | "";
address: string | "";
zip_code: string | "";
city: string | "";
status: number | -1;
id: number | -1;
role_id: number | -1;
date_of_birth: string | "";
company: string | "";
profile_picture: string | "";
payment_status: number | -1;
membership: Membership;
bank_account: BankAccount;
licence: Licence;
notes: string | "";
email: string | '';
first_name: string | '';
last_name: string | '';
phone: string | '';
notes: string | '';
address: string | '';
zip_code: string | '';
city: string | '';
status: number | -1;
id: number | -1;
role_id: number | -1;
dateofbirth: string | '';
company: string | '';
profile_picture: string | '';
payment_status: number | -1;
membership: Membership;
bank_account: BankAccount;
licence: Licence;
notes: string | '';
}
declare global {
namespace App {
// interface Error {}
interface Locals {
user: User;
users: User[];
subscriptions: Subscription[];
licence_categories: LicenceCategory[];
}
interface Types {
licenceCategory: LicenceCategory;
}
// interface PageData {}
// interface Platform {}
}
namespace App {
// interface Error {}
interface Locals {
user: User;
users: User[];
subscriptions: Subscription[];
licence_categories: LicenceCategory[];
}
interface Types {
licenceCategory: LicenceCategory;
}
// interface PageData {}
// interface Platform {}
}
}
export {};

View File

@@ -283,7 +283,6 @@
.input {
width: 100%;
margin: 0.5rem 0;
}
input,
textarea,
@@ -316,7 +315,6 @@
}
/* Add consistent spacing between input boxes */
.input-box {
margin: 1rem 0;
padding: 0.5rem;
background-color: var(--surface0);
border-radius: 6px;

View File

@@ -28,28 +28,6 @@
<div class="modal-background">
<div transition:modal|global={{ duration: 1000 }} class="modal" role="dialog" aria-modal="true">
<!-- svelte-ignore a11y-missing-attribute -->
<a
title={$t('cancel')}
class="modal-close"
on:click={closeModal}
role="button"
tabindex="0"
on:keydown={(e) => e.key == 'Enter' && closeModal()}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width="20"
height="20"
viewBox="0 0 384 512"
aria-hidden="true"
>
<path
d="M342.6 150.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0L192 210.7 86.6 105.4c-12.5-12.5-32.8-12.5-45.3 0s-12.5 32.8 0 45.3L146.7 256 41.4 361.4c-12.5 12.5-12.5 32.8 0 45.3s32.8 12.5 45.3 0L192 301.3 297.4 406.6c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L237.3 256 342.6 150.6z"
/>
</svg>
<span class="sr-only">{$t('cancel')}</span>
</a>
<div class="container">
<slot />
</div>
@@ -64,7 +42,7 @@
top: 0;
right: 0;
bottom: 0;
background: rgba(30, 30, 46, 0.65); /* var(--base) with 0.75 opacity */
background: var(--modal-backdrop); /* var(--base) with 0.75 opacity */
backdrop-filter: blur(4px); /* Optional: adds a slight blur effect */
z-index: 9999;
display: flex;
@@ -149,7 +127,6 @@
.modal .container {
flex-direction: column;
left: 0;
width: 100%;
}
}
</style>

View File

@@ -23,7 +23,7 @@
zip_code: '',
city: '',
company: '',
date_of_birth: '',
dateofbirth: '',
notes: '',
profile_picture: '',
payment_status: 0,
@@ -297,11 +297,11 @@
placeholder={$t('placeholder.phone')}
/>
<InputField
name="user[date_of_birth]"
name="user[dateofbirth]"
type="date"
label={$t('date_of_birth')}
bind:value={localUser.date_of_birth}
placeholder={$t('placeholder.date_of_birth')}
label={$t('dateofbirth')}
bind:value={localUser.dateofbirth}
placeholder={$t('placeholder.dateofbirth')}
/>
<InputField
name="user[address]"
@@ -529,7 +529,7 @@
.category-break {
grid-column: 1 / -1;
height: 1px;
background-color: var(--surface0);
background-color: var(--overlay0);
margin-top: 10px;
margin-left: 20%;
width: 60%;

View File

@@ -25,6 +25,7 @@
--base: #1e1e2e;
--mantle: #181825;
--crust: #11111b;
--modal-backdrop: rgba(49, 50, 68, 0.45); /* For Mocha theme */
}
@font-face {
@@ -385,8 +386,6 @@ li strong {
display: flex;
align-items: center;
justify-content: space-between;
margin: 10px 0;
padding: 10px;
width: 100%;
height: auto;
box-sizing: border-box;

View File

@@ -1,179 +1,176 @@
export default {
userStatus: {
1: "Nicht verifiziert",
2: "Verifiziert",
3: "Aktiv",
4: "Passiv",
5: "Deaktiviert",
},
userRole: {
0: "Mitglied",
1: "Betrachter",
4: "Bearbeiter",
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}...",
licence_number: "Auf dem Führerschein unter Feld 5",
issued_date: "Ausgabedatum unter Feld 4a",
expiration_date: "Ablaufdatum unter Feld 4b",
issuing_country: "Ausstellendes Land",
},
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",
licence: "Nummer zu kurz(11 Zeichen)",
},
server: {
error: {
invalid_json: "JSON Daten sind ungültig",
no_auth_token: "Nicht authorisiert, fehlender oder ungültiger Auth-Token",
jwt_parsing_error:
"Nicht authorisiert, Auth-Token konnte nicht gelesen werden",
unauthorized: "Sie sind nicht befugt diese Handlung durchzuführen",
internal_server_error:
"Verdammt, Fehler auf unserer Seite, probieren Sie es nochmal, danach rufen Sie jemanden vom Verein an.",
},
validation: {
invalid_user_id: "Nutzer ID ungültig",
invalid_subscription_model: "Model nicht gefunden",
user_not_found: "{field} konnte nicht gefunden werden",
invalid_user_data: "Nutzerdaten ungültig",
user_not_found_or_wrong_password:
"Existiert nicht oder falsches Passwort",
email_already_registered:
"Ein Mitglied wurde schon mit dieser Emailadresse erstellt.",
alphanumunicode: "beinhaltet nicht erlaubte Zeichen",
safe_content: "I see what you did there! Do not cross this line!",
iban: "Ungültig. Format: DE07123412341234123412",
bic: "Ungültig. Format: BELADEBEXXX",
email: "Format ungültig",
number: "Ist keine Nummer",
euDriversLicence: "Ist kein europäischer Führerschein",
lte: "Ist zu groß/neu",
gt: "Ist zu klein/alt",
required: "Feld wird benötigt",
image: "Dies ist kein Bild",
alphanum: "beinhaltet ungültige Zeichen",
alphaunicode: "darf nur aus Buchstaben bestehen",
},
},
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",
},
users: "Mitglieder",
user: {
login: "Nutzer Anmeldung",
edit: "Nutzer bearbeiten",
user: "Nutzer",
management: "Mitgliederverwaltung",
id: "Mitgliedsnr",
name: "Name",
email: "Email",
status: "Status",
role: "Nutzerrolle",
},
cancel: "Abbrechen",
confirm: "Bestätigen",
actions: "Aktionen",
edit: "Bearbeiten",
delete: "Löschen",
mandate_date_signed: "Mandatserteilungsdatum",
licence_categories: "Führerscheinklassen",
subscription_model: "Mitgliedschatfsmodell",
licence: "Führerschein",
licence_number: "Führerscheinnummer",
issued_date: "Ausgabedatum",
expiration_date: "Ablaufdatum",
country: "Land",
monthly_fee: "Monatliche Gebühr",
hourly_rate: "Stundensatz",
details: "Details",
conditions: "Bedingungen",
unknown: "Unbekannt",
notes: "Notizen",
address: "Straße & Hausnummer",
city: "Wohnort",
zip_code: "PLZ",
forgot_password: "Passwort vergessen?",
password: "Passwort",
password_repeat: "Passwort wiederholen",
email: "Email",
company: "Firma",
login: "Anmeldung",
profile: "Profil",
membership: "Mitgliedschaft",
bankaccount: "Kontodaten",
first_name: "Vorname",
last_name: "Nachname",
name: "Name",
phone: "Telefonnummer",
date_of_birth: "Geburtstag",
status: "Status",
start: "Beginn",
end: "Ende",
parent_member_id: "Hauptmitgliedsnr.",
bank_account_holder: "Kontoinhaber",
bank_name: "Bank",
iban: "IBAN",
bic: "BIC",
mandate_reference: "SEPA Mandat",
subscriptions: "Tarifmodelle",
payments: "Zahlungen",
add_new: "Neu",
included_hours_per_year: "Inkludierte Stunden pro Jahr",
included_hours_per_month: "Inkludierte Stunden pro Monat",
userStatus: {
1: 'Nicht verifiziert',
2: 'Verifiziert',
3: 'Aktiv',
4: 'Passiv',
5: 'Deaktiviert'
},
userRole: {
0: 'Mitglied',
1: 'Betrachter',
4: 'Bearbeiter',
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}...',
licence_number: 'Auf dem Führerschein unter Feld 5',
issued_date: 'Ausgabedatum unter Feld 4a',
expiration_date: 'Ablaufdatum unter Feld 4b',
issuing_country: 'Ausstellendes Land'
},
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',
licence: 'Nummer zu kurz(11 Zeichen)'
},
server: {
error: {
invalid_json: 'JSON Daten sind ungültig',
no_auth_token: 'Nicht authorisiert, fehlender oder ungültiger Auth-Token',
jwt_parsing_error: 'Nicht authorisiert, Auth-Token konnte nicht gelesen werden',
unauthorized: 'Sie sind nicht befugt diese Handlung durchzuführen',
internal_server_error:
'Verdammt, Fehler auf unserer Seite, probieren Sie es nochmal, danach rufen Sie jemanden vom Verein an.'
},
validation: {
invalid_user_id: 'Nutzer ID ungültig',
invalid_subscription_model: 'Model nicht gefunden',
user_not_found: '{field} konnte nicht gefunden werden',
invalid_user_data: 'Nutzerdaten ungültig',
user_not_found_or_wrong_password: 'Existiert nicht oder falsches Passwort',
email_already_registered: 'Ein Mitglied wurde schon mit dieser Emailadresse erstellt.',
alphanumunicode: 'beinhaltet nicht erlaubte Zeichen',
safe_content: 'I see what you did there! Do not cross this line!',
iban: 'Ungültig. Format: DE07123412341234123412',
bic: 'Ungültig. Format: BELADEBEXXX',
email: 'Format ungültig',
number: 'Ist keine Nummer',
euDriversLicence: 'Ist kein europäischer Führerschein',
lte: 'Ist zu groß/neu',
gt: 'Ist zu klein/alt',
required: 'Feld wird benötigt',
image: 'Dies ist kein Bild',
alphanum: 'beinhaltet ungültige Zeichen',
alphaunicode: 'darf nur aus Buchstaben bestehen'
}
},
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'
},
users: 'Mitglieder',
user: {
login: 'Nutzer Anmeldung',
edit: 'Nutzer bearbeiten',
user: 'Nutzer',
management: 'Mitgliederverwaltung',
id: 'Mitgliedsnr',
name: 'Name',
email: 'Email',
status: 'Status',
role: 'Nutzerrolle'
},
cancel: 'Abbrechen',
confirm: 'Bestätigen',
actions: 'Aktionen',
edit: 'Bearbeiten',
delete: 'Löschen',
mandate_date_signed: 'Mandatserteilungsdatum',
licence_categories: 'Führerscheinklassen',
subscription_model: 'Mitgliedschatfsmodell',
licence: 'Führerschein',
licence_number: 'Führerscheinnummer',
issued_date: 'Ausgabedatum',
expiration_date: 'Ablaufdatum',
country: 'Land',
monthly_fee: 'Monatliche Gebühr',
hourly_rate: 'Stundensatz',
details: 'Details',
conditions: 'Bedingungen',
unknown: 'Unbekannt',
notes: 'Notizen',
address: 'Straße & Hausnummer',
city: 'Wohnort',
zip_code: 'PLZ',
forgot_password: 'Passwort vergessen?',
password: 'Passwort',
password_repeat: 'Passwort wiederholen',
email: 'Email',
company: 'Firma',
login: 'Anmeldung',
profile: 'Profil',
membership: 'Mitgliedschaft',
bankaccount: 'Kontodaten',
first_name: 'Vorname',
last_name: 'Nachname',
name: 'Name',
phone: 'Telefonnummer',
dateofbirth: 'Geburtstag',
status: 'Status',
start: 'Beginn',
end: 'Ende',
parent_member_id: 'Hauptmitgliedsnr.',
bank_account_holder: 'Kontoinhaber',
bank_name: 'Bank',
iban: 'IBAN',
bic: 'BIC',
mandate_reference: 'SEPA Mandat',
subscriptions: 'Tarifmodelle',
payments: 'Zahlungen',
add_new: 'Neu',
included_hours_per_year: 'Inkludierte Stunden pro Jahr',
included_hours_per_month: 'Inkludierte Stunden pro Monat',
// For payments section
payment: {
id: "Zahlungs-Nr",
amount: "Betrag",
date: "Datum",
status: "Status",
},
// For payments section
payment: {
id: 'Zahlungs-Nr',
amount: 'Betrag',
date: 'Datum',
status: 'Status'
},
// For subscription statuses
subscriptionStatus: {
pending: "Ausstehend",
completed: "Abgeschlossen",
failed: "Fehlgeschlagen",
cancelled: "Storniert",
},
// For subscription statuses
subscriptionStatus: {
pending: 'Ausstehend',
completed: 'Abgeschlossen',
failed: 'Fehlgeschlagen',
cancelled: 'Storniert'
}
};

View File

@@ -1,24 +1,24 @@
// @ts-nocheck
import { quintOut } from "svelte/easing";
import { crossfade } from "svelte/transition";
import { quintOut } from 'svelte/easing';
import { crossfade } from 'svelte/transition';
export const [send, receive] = crossfade({
duration: (d) => Math.sqrt(d * 200),
duration: (d) => Math.sqrt(d * 200),
// eslint-disable-next-line no-unused-vars
fallback(node, params) {
const style = getComputedStyle(node);
const transform = style.transform === "none" ? "" : style.transform;
// eslint-disable-next-line no-unused-vars
fallback(node, params) {
const style = getComputedStyle(node);
const transform = style.transform === 'none' ? '' : style.transform;
return {
duration: 600,
easing: quintOut,
css: (t) => `
return {
duration: 600,
easing: quintOut,
css: (t) => `
transform: ${transform} scale(${t});
opacity: ${t}
`,
};
},
`
};
}
});
/**
@@ -27,9 +27,9 @@ export const [send, receive] = crossfade({
* @param {string} email - The email to validate
*/
export const isValidEmail = (email) => {
const EMAIL_REGEX =
/[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/;
return EMAIL_REGEX.test(email.trim());
const EMAIL_REGEX =
/[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/;
return EMAIL_REGEX.test(email.trim());
};
/**
* Validates a strong password field
@@ -37,11 +37,9 @@ export const isValidEmail = (email) => {
* @param {string} password - The password to validate
*/
export const isValidPasswordStrong = (password) => {
const strongRegex = new RegExp(
"^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*])(?=.{8,})"
);
const strongRegex = new RegExp('^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.*[!@#$%^&*])(?=.{8,})');
return strongRegex.test(password.trim());
return strongRegex.test(password.trim());
};
/**
* Validates a medium password field
@@ -49,11 +47,11 @@ export const isValidPasswordStrong = (password) => {
* @param {string} password - The password to validate
*/
export const isValidPasswordMedium = (password) => {
const mediumRegex = new RegExp(
"^(((?=.*[a-z])(?=.*[A-Z]))|((?=.*[a-z])(?=.*[0-9]))|((?=.*[A-Z])(?=.*[0-9])))(?=.{6,})"
);
const mediumRegex = new RegExp(
'^(((?=.*[a-z])(?=.*[A-Z]))|((?=.*[a-z])(?=.*[0-9]))|((?=.*[A-Z])(?=.*[0-9])))(?=.{6,})'
);
return mediumRegex.test(password.trim());
return mediumRegex.test(password.trim());
};
/**
@@ -63,22 +61,22 @@ export const isValidPasswordMedium = (password) => {
*/
export function isEmpty(obj) {
for (const _i in obj) {
return false;
}
return true;
for (const _i in obj) {
return false;
}
return true;
}
export function toRFC3339(dateString) {
if (!dateString) dateString = "0001-01-01T00:00:00.000Z";
const date = new Date(dateString);
return date.toISOString();
if (!dateString) dateString = '0001-01-01T00:00:00.000Z';
const date = new Date(dateString);
return date.toISOString();
}
export function fromRFC3339(dateString) {
if (!dateString) dateString = "0001-01-01T00:00:00.000Z";
const date = new Date(dateString);
return date.toISOString().split("T")[0];
if (!dateString) dateString = '0001-01-01T00:00:00.000Z';
const date = new Date(dateString);
return date.toISOString().split('T')[0];
}
/**
@@ -86,28 +84,26 @@ export function fromRFC3339(dateString) {
* @param {App.Locals.User} user - The user object to format
*/
export function userDatesFromRFC3339(user) {
if (user.date_of_birth) {
user.date_of_birth = fromRFC3339(user.date_of_birth);
}
if (user.membership) {
if (user.membership.start_date) {
user.membership.start_date = fromRFC3339(user.membership.start_date);
}
if (user.membership.end_date) {
user.membership.end_date = fromRFC3339(user.membership.end_date);
}
}
if (user.licence?.issued_date) {
user.licence.issued_date = fromRFC3339(user.licence.issued_date);
}
if (user.licence?.expiration_date) {
user.licence.expiration_date = fromRFC3339(user.licence.expiration_date);
}
if (user.bank_account && user.bank_account.mandate_date_signed) {
user.bank_account.mandate_date_signed = fromRFC3339(
user.bank_account.mandate_date_signed
);
}
if (user.dateofbirth) {
user.dateofbirth = fromRFC3339(user.dateofbirth);
}
if (user.membership) {
if (user.membership.start_date) {
user.membership.start_date = fromRFC3339(user.membership.start_date);
}
if (user.membership.end_date) {
user.membership.end_date = fromRFC3339(user.membership.end_date);
}
}
if (user.licence?.issued_date) {
user.licence.issued_date = fromRFC3339(user.licence.issued_date);
}
if (user.licence?.expiration_date) {
user.licence.expiration_date = fromRFC3339(user.licence.expiration_date);
}
if (user.bank_account && user.bank_account.mandate_date_signed) {
user.bank_account.mandate_date_signed = fromRFC3339(user.bank_account.mandate_date_signed);
}
}
/**
@@ -115,28 +111,26 @@ export function userDatesFromRFC3339(user) {
* @param {App.Locals.User} user - The user object to format
*/
export function userDatesToRFC3339(user) {
if (user.date_of_birth) {
user.date_of_birth = toRFC3339(user.date_of_birth);
}
if (user.membership) {
if (user.membership.start_date) {
user.membership.start_date = toRFC3339(user.membership.start_date);
}
if (user.membership.end_date) {
user.membership.end_date = toRFC3339(user.membership.end_date);
}
}
if (user.licence?.issued_date) {
user.licence.issued_date = toRFC3339(user.licence.issued_date);
}
if (user.licence?.expiration_date) {
user.licence.expiration_date = toRFC3339(user.licence.expiration_date);
}
if (user.bank_account && user.bank_account.mandate_date_signed) {
user.bank_account.mandate_date_signed = toRFC3339(
user.bank_account.mandate_date_signed
);
}
if (user.dateofbirth) {
user.dateofbirth = toRFC3339(user.dateofbirth);
}
if (user.membership) {
if (user.membership.start_date) {
user.membership.start_date = toRFC3339(user.membership.start_date);
}
if (user.membership.end_date) {
user.membership.end_date = toRFC3339(user.membership.end_date);
}
}
if (user.licence?.issued_date) {
user.licence.issued_date = toRFC3339(user.licence.issued_date);
}
if (user.licence?.expiration_date) {
user.licence.expiration_date = toRFC3339(user.licence.expiration_date);
}
if (user.bank_account && user.bank_account.mandate_date_signed) {
user.bank_account.mandate_date_signed = toRFC3339(user.bank_account.mandate_date_signed);
}
}
/**
@@ -145,33 +139,33 @@ export function userDatesToRFC3339(user) {
* @returns {array} The formatted error object
*/
export function formatError(obj) {
const errors = [];
if (typeof obj === "object" && obj !== null) {
if (Array.isArray(obj)) {
obj.forEach((error) => {
errors.push({
field: error.field,
key: error.key,
id: Math.random() * 1000,
});
});
} else {
Object.keys(obj).forEach((field) => {
errors.push({
field: field,
key: obj[field].key,
id: Math.random() * 1000,
});
});
}
} else {
errors.push({
field: "general",
key: obj,
id: 0,
});
}
return errors;
const errors = [];
if (typeof obj === 'object' && obj !== null) {
if (Array.isArray(obj)) {
obj.forEach((error) => {
errors.push({
field: error.field,
key: error.key,
id: Math.random() * 1000
});
});
} else {
Object.keys(obj).forEach((field) => {
errors.push({
field: field,
key: obj[field].key,
id: Math.random() * 1000
});
});
}
} else {
errors.push({
field: 'general',
key: obj,
id: 0
});
}
return errors;
}
/**
@@ -180,26 +174,26 @@ export function formatError(obj) {
* @param {import('RequestEvent<Partial<Record<string, string>>, string | null>')} event - The event object
*/
export function refreshCookie(newToken, event) {
if (newToken) {
const match = newToken.match(/jwt=([^;]+)/);
if (match) {
if (event) {
event.cookies.set("jwt", match[1], {
path: "/",
httpOnly: true,
secure: process.env.NODE_ENV === "production", // Secure in production
sameSite: "lax",
maxAge: 5 * 24 * 60 * 60, // 5 days in seconds
});
} else {
cookies.set("jwt", match[1], {
path: "/",
httpOnly: true,
secure: process.env.NODE_ENV === "production", // Secure in production
sameSite: "lax",
maxAge: 5 * 24 * 60 * 60, // 5 days in seconds
});
}
}
}
if (newToken) {
const match = newToken.match(/jwt=([^;]+)/);
if (match) {
if (event) {
event.cookies.set('jwt', match[1], {
path: '/',
httpOnly: true,
secure: process.env.NODE_ENV === 'production', // Secure in production
sameSite: 'lax',
maxAge: 5 * 24 * 60 * 60 // 5 days in seconds
});
} else {
cookies.set('jwt', match[1], {
path: '/',
httpOnly: true,
secure: process.env.NODE_ENV === 'production', // Secure in production
sameSite: 'lax',
maxAge: 5 * 24 * 60 * 60 // 5 days in seconds
});
}
}
}
}

View File

@@ -0,0 +1,121 @@
import { toRFC3339 } from './helpers';
/**
* Converts FormData to a nested object structure
* @param {FormData} formData - The FormData object to convert
* @returns {{ user: Partial<App.Locals['user']> }} Nested object representation of the form data
*/
export function formDataToObject(formData) {
/** @type { Partial<App.Locals['user']> } */
const object = {};
console.log('Form data entries:');
for (const [key, value] of formData.entries()) {
console.log('Key:', key, 'Value:', value);
}
for (const [key, value] of formData.entries()) {
/** @type {string[]} */
const keys = key.match(/\[([^\]]+)\]/g)?.map((k) => k.slice(1, -1)) || [key];
console.log('Processed keys:', keys);
/** @type {Record<string, any>} */
let current = object;
console.log('Current object state:', JSON.stringify(current));
for (let i = 0; i < keys.length - 1; i++) {
/**
* Create nested object if it doesn't exist
* @type {Record<string, any>}
* @description Ensures proper nesting structure for user data fields
* @example
* // For input name="user[membership][status]"
* // Creates: { user: { membership: { status: value } } }
*/
current[keys[i]] = current[keys[i]] || {};
/**
* Move to the next level of the object
* @type {Record<string, any>}
*/
current = current[keys[i]];
}
const lastKey = keys[keys.length - 1];
if (lastKey.endsWith('[]')) {
/**
* Handle array fields (licence categories)
*/
const arrayKey = lastKey.slice(0, -2);
current[arrayKey] = current[arrayKey] || [];
current[arrayKey].push(value);
} else {
current[lastKey] = value;
}
}
return { user: object };
}
/**
* Processes the raw form data into the expected user data structure
* @param {{ user: Partial<App.Locals['user']> } } rawData - The raw form data object
* @returns {{ user: Partial<App.Locals['user']> }} Processed user data
*/
export function processFormData(rawData) {
/** @type {{ user: Partial<App.Locals['user']> }} */
const processedData = {
user: {
id: Number(rawData.user.id) || 0,
status: Number(rawData.user.status),
role_id: Number(rawData.user.role_id),
first_name: String(rawData.user.first_name),
last_name: String(rawData.user.last_name),
email: String(rawData.user.email),
phone: String(rawData.user.phone || ''),
company: String(rawData.user.company || ''),
dateofbirth: toRFC3339(rawData.user.dateofbirth),
address: String(rawData.user.address || ''),
zip_code: String(rawData.user.zip_code || ''),
city: String(rawData.user.city || ''),
notes: String(rawData.user.notes || ''),
profile_picture: String(rawData.user.profile_picture || ''),
membership: {
id: Number(rawData.user.membership?.id) || 0,
status: Number(rawData.user.membership?.status),
start_date: toRFC3339(rawData.user.membership?.start_date),
end_date: toRFC3339(rawData.user.membership?.end_date),
parent_member_id: Number(rawData.user.membership?.parent_member_id) || 0,
subscription_model: {
id: Number(rawData.user.membership?.subscription_model?.id) || 0,
name: String(rawData.user.membership?.subscription_model?.name) || ''
}
},
licence: {
id: Number(rawData.user.licence?.id) || 0,
status: Number(rawData.user.licence?.status),
licence_number: String(rawData.user.licence?.licence_number || ''),
issued_date: toRFC3339(rawData.user.licence?.issued_date),
expiration_date: toRFC3339(rawData.user.licence?.expiration_date),
country: String(rawData.user.licence?.country || ''),
licence_categories: rawData.user.licence?.licence_categories || []
},
bank_account: {
id: Number(rawData.user.bank_account?.id) || 0,
account_holder_name: String(rawData.user.bank_account?.account_holder_name || ''),
bank: String(rawData.user.bank_account?.bank || ''),
iban: String(rawData.user.bank_account?.iban || ''),
bic: String(rawData.user.bank_account?.bic || ''),
mandate_reference: String(rawData.user.bank_account?.mandate_reference || ''),
mandate_date_signed: toRFC3339(rawData.user.bank_account?.mandate_date_signed)
}
}
};
// Remove undefined or null properties
const cleanUpdateData = JSON.parse(JSON.stringify(processedData), (key, value) =>
value !== null && value !== '' ? value : undefined
);
console.dir(cleanUpdateData);
return cleanUpdateData;
}

View File

@@ -1,11 +1,7 @@
import { BASE_API_URI } from "$lib/utils/constants";
import {
formatError,
userDatesFromRFC3339,
userDatesToRFC3339,
} from "$lib/utils/helpers";
import { fail, redirect } from "@sveltejs/kit";
import { toRFC3339 } from "$lib/utils/helpers";
import { BASE_API_URI } from '$lib/utils/constants';
import { formatError, userDatesFromRFC3339 } from '$lib/utils/helpers';
import { fail, redirect } from '@sveltejs/kit';
import { formDataToObject, processFormData } from '$lib/utils/processing';
/**
* @typedef {Object} UpdateData
@@ -14,187 +10,125 @@ import { toRFC3339 } from "$lib/utils/helpers";
/** @type {import('./$types').PageServerLoad} */
export async function load({ locals, params }) {
// redirect user if not logged in
if (!locals.user) {
throw redirect(302, `/auth/login?next=/auth/about/${params.id}`);
}
// redirect user if not logged in
if (!locals.user) {
throw redirect(302, `/auth/login?next=/auth/about/${params.id}`);
}
}
/** @type {import('./$types').Actions} */
export const actions = {
/**
*
* @param request - The request object
* @param fetch - Fetch object from sveltekit
* @param cookies - SvelteKit's cookie object
* @param locals - The local object, housing current user
* @returns Error data or redirects user to the home page or the previous page
*/
updateUser: async ({ request, fetch, cookies, locals }) => {
let formData = await request.formData();
/**
*
* @param request - The request object
* @param fetch - Fetch object from sveltekit
* @param cookies - SvelteKit's cookie object
* @param locals - The local object, housing current user
* @returns Error data or redirects user to the home page or the previous page
*/
updateUser: async ({ request, fetch, cookies, locals }) => {
let formData = await request.formData();
/** @type {App.Types['licenceCategory'][]} */
const licenceCategories = formData
.getAll("licence_categories[]")
.filter((value) => typeof value === "string")
.map((value) => {
try {
return JSON.parse(value);
} catch (e) {
console.error("Failed to parse licence category:", value);
return null;
}
});
const rawData = formDataToObject(formData);
const processedData = processFormData(rawData);
/** @type {Partial<App.Locals['user']>} */
const userData = {
id: Number(formData.get("id")),
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("date_of_birth")),
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: toRFC3339(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")),
},
licence: {
id: Number(formData.get("drivers_licence_id")),
status: Number(formData.get("licence_status")),
licence_number: String(formData.get("licence_number")),
issued_date: toRFC3339(formData.get("issued_date")),
expiration_date: toRFC3339(formData.get("expiration_date")),
country: String(formData.get("country")),
licence_categories: licenceCategories,
},
};
console.dir(processedData.user.membership);
const isCreating = !processedData.user.id || processedData.user.id === 0;
console.log('Is updating: ', isCreating);
console.dir(formData);
const apiURL = `${BASE_API_URI}/backend/users/update/`;
// userDatesToRFC3339(userData);
/** @type {RequestInit} */
const requestUpdateOptions = {
method: 'PATCH',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
Cookie: `jwt=${cookies.get('jwt')}`
},
body: JSON.stringify(processedData)
};
const res = await fetch(apiURL, requestUpdateOptions);
/** @type {UpdateData} */
const updateData = { user: userData };
// Remove undefined or null properties
const cleanUpdateData = JSON.parse(
JSON.stringify(updateData),
(key, value) => (value !== null && value !== "" ? value : undefined)
);
if (!res.ok) {
const response = await res.json();
const errors = formatError(response.errors);
return fail(400, { errors: errors });
}
console.dir(formData);
console.dir(cleanUpdateData);
const apiURL = `${BASE_API_URI}/backend/users/update/`;
const response = await res.json();
locals.user = response;
userDatesFromRFC3339(locals.user);
throw redirect(303, `/auth/about/${response.id}`);
},
/** @type {RequestInit} */
const requestUpdateOptions = {
method: "PATCH",
credentials: "include",
headers: {
"Content-Type": "application/json",
Cookie: `jwt=${cookies.get("jwt")}`,
},
body: JSON.stringify(cleanUpdateData),
};
const res = await fetch(apiURL, requestUpdateOptions);
/**
*
* @param request - The request object
* @param fetch - Fetch object from sveltekit
* @param cookies - SvelteKit's cookie object
* @param locals - The local object, housing current user
* @returns Error data or redirects user to the home page or the previous page
*/
uploadImage: async ({ request, fetch, cookies }) => {
const formData = await request.formData();
if (!res.ok) {
const response = await res.json();
const errors = formatError(response.errors);
return fail(400, { errors: errors });
}
/** @type {RequestInit} */
const requestInitOptions = {
method: 'POST',
headers: {
Cookie: `jwt=${cookies.get('jwt')}`
},
body: formData
};
const response = await res.json();
locals.user = response;
userDatesFromRFC3339(locals.user);
throw redirect(303, `/auth/about/${response.id}`);
},
/**
*
* @param request - The request object
* @param fetch - Fetch object from sveltekit
* @param cookies - SvelteKit's cookie object
* @param locals - The local object, housing current user
* @returns Error data or redirects user to the home page or the previous page
*/
uploadImage: async ({ request, fetch, cookies }) => {
const formData = await request.formData();
const res = await fetch(`${BASE_API_URI}/file/upload/`, requestInitOptions);
/** @type {RequestInit} */
const requestInitOptions = {
method: "POST",
headers: {
Cookie: `jwt=${cookies.get("jwt")}`,
},
body: formData,
};
if (!res.ok) {
const response = await res.json();
const errors = formatError(response.errors);
return fail(400, { errors: errors });
}
const res = await fetch(`${BASE_API_URI}/file/upload/`, requestInitOptions);
const response = await res.json();
if (!res.ok) {
const response = await res.json();
const errors = formatError(response.errors);
return fail(400, { errors: errors });
}
return {
success: true,
profile_picture: response['']
};
},
const response = await res.json();
/**
*
* @param request - The request object
* @param fetch - Fetch object from sveltekit
* @param cookies - SvelteKit's cookie object
* @param locals - The local object, housing current user
* @returns Error data or redirects user to the home page or the previous page
*/
deleteImage: async ({ request, fetch, cookies }) => {
const formData = await request.formData();
return {
success: true,
profile_picture: response[""],
};
},
/** @type {RequestInit} */
const requestInitOptions = {
method: 'DELETE',
headers: {
Cookie: `jwt=${cookies.get('jwt')}`
},
body: formData
};
/**
*
* @param request - The request object
* @param fetch - Fetch object from sveltekit
* @param cookies - SvelteKit's cookie object
* @param locals - The local object, housing current user
* @returns Error data or redirects user to the home page or the previous page
*/
deleteImage: async ({ request, fetch, cookies }) => {
const formData = await request.formData();
const res = await fetch(`${BASE_API_URI}/file/delete/`, requestInitOptions);
/** @type {RequestInit} */
const requestInitOptions = {
method: "DELETE",
headers: {
Cookie: `jwt=${cookies.get("jwt")}`,
},
body: formData,
};
if (!res.ok) {
const response = await res.json();
const errors = formatError(response.errors);
return fail(400, { errors: errors });
}
const res = await fetch(`${BASE_API_URI}/file/delete/`, requestInitOptions);
if (!res.ok) {
const response = await res.json();
const errors = formatError(response.errors);
return fail(400, { errors: errors });
}
return {
success: true,
profile_picture: "",
};
},
return {
success: true,
profile_picture: ''
};
}
};

View File

@@ -65,10 +65,10 @@
<span class="value">{user.phone}</span>
</h3>
{/if}
{#if user.date_of_birth}
{#if user.dateofbirth}
<h3 class="hero-subtitle subtitle info-row">
<span class="label">Geburtstag:</span>
<span class="value">{user.date_of_birth}</span>
<span class="value">{user.dateofbirth}</span>
</h3>
{/if}
{#if user.notes}

View File

@@ -2,194 +2,63 @@
// - Implement a load function to fetch a list of all users.
// - Create actions for updating user information (similar to the about/[id] route).
import { BASE_API_URI } from "$lib/utils/constants";
import { formatError, userDatesFromRFC3339 } from "$lib/utils/helpers";
import { fail, redirect } from "@sveltejs/kit";
import { toRFC3339 } from "$lib/utils/helpers";
/**
* Converts FormData to a nested object structure
* @param {FormData} formData - The FormData object to convert
* @returns {{ user: Partial<App.Locals['user']> }} Nested object representation of the form data
*/
function formDataToObject(formData) {
/** @type { Partial<App.Locals['user']> } */
const object = {};
console.log("Form data entries:");
for (const [key, value] of formData.entries()) {
console.log("Key:", key, "Value:", value);
}
for (const [key, value] of formData.entries()) {
/** @type {string[]} */
const keys = key.match(/\[([^\]]+)\]/g)?.map((k) => k.slice(1, -1)) || [
key,
];
console.log("Processed keys:", keys);
/** @type {Record<string, any>} */
let current = object;
console.log("Current object state:", JSON.stringify(current));
for (let i = 0; i < keys.length - 1; i++) {
/**
* Create nested object if it doesn't exist
* @type {Record<string, any>}
* @description Ensures proper nesting structure for user data fields
* @example
* // For input name="user[membership][status]"
* // Creates: { user: { membership: { status: value } } }
*/
current[keys[i]] = current[keys[i]] || {};
/**
* Move to the next level of the object
* @type {Record<string, any>}
*/
current = current[keys[i]];
}
const lastKey = keys[keys.length - 1];
if (lastKey.endsWith("[]")) {
/**
* Handle array fields (licence categories)
*/
const arrayKey = lastKey.slice(0, -2);
current[arrayKey] = current[arrayKey] || [];
current[arrayKey].push(value);
} else {
current[lastKey] = value;
}
}
return { user: object };
}
/**
* Processes the raw form data into the expected user data structure
* @param {{ user: Partial<App.Locals['user']> } } rawData - The raw form data object
* @returns {{ user: Partial<App.Locals['user']> }} Processed user data
*/
function processFormData(rawData) {
/** @type {{ user: Partial<App.Locals['user']> }} */
const processedData = {
user: {
id: Number(rawData.user.id) || 0,
status: Number(rawData.user.status),
role_id: Number(rawData.user.role_id),
first_name: String(rawData.user.first_name),
last_name: String(rawData.user.last_name),
email: String(rawData.user.email),
phone: String(rawData.user.phone || ""),
company: String(rawData.user.company || ""),
date_of_birth: toRFC3339(rawData.user.date_of_birth),
address: String(rawData.user.address || ""),
zip_code: String(rawData.user.zip_code || ""),
city: String(rawData.user.city || ""),
notes: String(rawData.user.notes || ""),
profile_picture: String(rawData.user.profile_picture || ""),
membership: {
id: Number(rawData.user.membership?.id) || 0,
status: Number(rawData.user.membership?.status),
start_date: toRFC3339(rawData.user.membership?.start_date),
end_date: toRFC3339(rawData.user.membership?.end_date),
parent_member_id:
Number(rawData.user.membership?.parent_member_id) || 0,
subscription_model: {
id: Number(rawData.user.membership?.subscription_model?.id) || 0,
name: String(rawData.user.membership?.subscription_model?.name) || "",
},
},
licence: {
id: Number(rawData.user.licence?.id) || 0,
status: Number(rawData.user.licence?.status),
licence_number: String(rawData.user.licence?.licence_number || ""),
issued_date: toRFC3339(rawData.user.licence?.issued_date),
expiration_date: toRFC3339(rawData.user.licence?.expiration_date),
country: String(rawData.user.licence?.country || ""),
licence_categories: rawData.user.licence?.licence_categories || [],
},
bank_account: {
id: Number(rawData.user.bank_account?.id) || 0,
account_holder_name: String(
rawData.user.bank_account?.account_holder_name || ""
),
bank: String(rawData.user.bank_account?.bank || ""),
iban: String(rawData.user.bank_account?.iban || ""),
bic: String(rawData.user.bank_account?.bic || ""),
mandate_reference: String(
rawData.user.bank_account?.mandate_reference || ""
),
mandate_date_signed: toRFC3339(
rawData.user.bank_account?.mandate_date_signed
),
},
},
};
return processedData;
}
import { BASE_API_URI } from '$lib/utils/constants';
import { formatError, userDatesFromRFC3339 } from '$lib/utils/helpers';
import { fail, redirect } from '@sveltejs/kit';
import { formDataToObject, processFormData } from '$lib/utils/processing';
/** @type {import('./$types').PageServerLoad} */
export async function load({ locals, params }) {
// redirect user if not logged in
if (!locals.user) {
throw redirect(302, `/auth/login?next=/auth/users`);
}
// redirect user if not logged in
if (!locals.user) {
throw redirect(302, `/auth/login?next=/auth/users`);
}
}
/** @type {import('./$types').Actions} */
export const actions = {
/**
*
* @param request - The request object
* @param fetch - Fetch object from sveltekit
* @param cookies - SvelteKit's cookie object
* @param locals - The local object, housing current user
* @returns Error data or redirects user to the home page or the previous page
*/
updateUser: async ({ request, fetch, cookies, locals }) => {
let formData = await request.formData();
/**
*
* @param request - The request object
* @param fetch - Fetch object from sveltekit
* @param cookies - SvelteKit's cookie object
* @param locals - The local object, housing current user
* @returns Error data or redirects user to the home page or the previous page
*/
updateUser: async ({ request, fetch, cookies, locals }) => {
let formData = await request.formData();
// Convert form data to nested object
const rawData = formDataToObject(formData);
const rawData = formDataToObject(formData);
const processedData = processFormData(rawData);
const processedData = processFormData(rawData);
console.dir(processedData.user.membership);
const isCreating = !processedData.user.id || processedData.user.id === 0;
console.log('Is creating: ', isCreating);
const apiURL = `${BASE_API_URI}/backend/users/update`;
// Remove undefined or null properties
const cleanUpdateData = JSON.parse(
JSON.stringify(processedData),
(key, value) => (value !== null && value !== "" ? value : undefined)
);
console.dir(processedData.user.membership);
const isCreating = !processedData.user.id || processedData.user.id === 0;
console.log("Is creating: ", isCreating);
const apiURL = `${BASE_API_URI}/backend/users/update`;
/** @type {RequestInit} */
const requestOptions = {
method: isCreating ? 'POST' : 'PATCH',
credentials: 'include',
headers: {
'Content-Type': 'application/json',
Cookie: `jwt=${cookies.get('jwt')}`
},
body: JSON.stringify(processedData)
};
/** @type {RequestInit} */
const requestOptions = {
method: isCreating ? "POST" : "PATCH",
credentials: "include",
headers: {
"Content-Type": "application/json",
Cookie: `jwt=${cookies.get("jwt")}`,
},
body: JSON.stringify(cleanUpdateData),
};
const res = await fetch(apiURL, requestOptions);
const res = await fetch(apiURL, requestOptions);
if (!res.ok) {
const response = await res.json();
const errors = formatError(response.errors);
return fail(400, { errors: errors });
}
if (!res.ok) {
const response = await res.json();
const errors = formatError(response.errors);
return fail(400, { errors: errors });
}
const response = await res.json();
console.log("Server success response:", response);
locals.user = response;
userDatesFromRFC3339(locals.user);
throw redirect(303, `/auth/about/${response.id}`);
},
const response = await res.json();
console.log('Server success response:', response);
locals.user = response;
userDatesFromRFC3339(locals.user);
throw redirect(303, `/auth/about/${response.id}`);
}
};

View File

@@ -58,8 +58,8 @@
on:click={() => setActiveSection('users')}
>
<i class="fas fa-users"></i>
<span class="nav-badge">{users.length}</span>
{$t('users')}
<span class="nav-badge">{users.length}</span>
</button>
</li>
<li>
@@ -68,8 +68,8 @@
on:click={() => setActiveSection('subscriptions')}
>
<i class="fas fa-clipboard-list"></i>
<span class="nav-badge">{subscriptions.length}</span>
{$t('subscriptions')}
<span class="nav-badge">{subscriptions.length}</span>
</button>
</li>
<li>
@@ -238,7 +238,7 @@
width: 100%;
height: 100%;
padding: 0 1rem;
color: white;
color: var(--text);
}
.layout {
@@ -252,8 +252,8 @@
.sidebar {
width: 250px;
min-height: 600px;
background: #2f2f2f;
border-right: 1px solid #494848;
background: var(--surface0);
border-right: 1px solid var(--surface1);
}
.nav-list {
@@ -272,7 +272,7 @@
background: none;
text-align: left;
cursor: pointer;
color: #9b9b9b;
color: var(--subtext0);
text-transform: uppercase;
font-weight: 500;
letter-spacing: 1px;
@@ -280,12 +280,14 @@
}
.nav-link:hover {
background: #fdfff5;
background: var(--surface1);
color: var(--text);
}
.nav-link.active {
background: #494848;
color: white;
background: var(--surface2);
color: var(--lavender);
border-left: 3px solid var(--mauve);
}
.main-content {
@@ -295,20 +297,29 @@
.accordion-item {
border: none;
background: #2f2f2f;
background: var(--surface0);
margin-bottom: 0.5rem;
border-radius: 8px;
overflow: hidden;
}
.accordion-header {
padding: 1rem;
cursor: pointer;
font-family: 'Roboto Mono', monospace;
color: white;
color: var(--text);
background: var(--surface1);
transition: background-color 0.2s ease-in-out;
}
.accordion-header:hover {
background: var(--surface2);
}
.accordion-content {
padding: 1rem;
background: #494848;
background: var(--surface0);
border-top: 1px solid var(--surface1);
}
.table {
@@ -325,11 +336,11 @@
}
.table th {
color: #9b9b9b;
color: var(--subtext1);
}
.table td {
color: white;
color: var(--text);
}
@media (max-width: 680px) {
@@ -358,5 +369,42 @@
.section-header h2 {
margin: 0;
color: var(--lavender);
}
/* Additional styles for better visual hierarchy */
details[open] .accordion-header {
background: var(--surface2);
color: var(--lavender);
}
.button-group {
margin-top: 1rem;
display: flex;
gap: 0.5rem;
}
/* Style for the nav badge */
.nav-badge {
background: var(--surface2);
color: var(--text);
padding: 0.2rem 0.5rem;
border-radius: 4px;
font-size: 0.8rem;
margin-left: auto;
}
/* Improved focus states */
.nav-link:focus,
.accordion-header:focus {
outline: 2px solid var(--mauve);
outline-offset: -2px;
border-radius: 8px;
}
/* Add subtle transitions */
.accordion-item,
.accordion-header,
.nav-link {
transition: all 0.2s ease-in-out;
}
</style>

View File

@@ -13,7 +13,7 @@ type User struct {
CreatedAt time.Time
UpdatedAt time.Time
DeletedAt *time.Time `gorm:"index"`
DateOfBirth time.Time `gorm:"not null" json:"date_of_birth" binding:"required,safe_content"`
DateOfBirth time.Time `gorm:"not null" json:"dateofbirth" binding:"required,safe_content"`
Company string `json:"company" binding:"omitempty,omitnil,safe_content"`
Phone string `json:"phone" binding:"omitempty,omitnil,safe_content"`
Notes string `json:"notes" binding:"safe_content"`
@@ -63,19 +63,19 @@ func (u *User) PasswordMatches(plaintextPassword string) (bool, error) {
func (u *User) Safe() map[string]interface{} {
result := map[string]interface{}{
"email": u.Email,
"first_name": u.FirstName,
"last_name": u.LastName,
"phone": u.Phone,
"notes": u.Notes,
"address": u.Address,
"zip_code": u.ZipCode,
"city": u.City,
"status": u.Status,
"id": u.ID,
"role_id": u.RoleID,
"company": u.Company,
"date_of_birth": u.DateOfBirth,
"email": u.Email,
"first_name": u.FirstName,
"last_name": u.LastName,
"phone": u.Phone,
"notes": u.Notes,
"address": u.Address,
"zip_code": u.ZipCode,
"city": u.City,
"status": u.Status,
"id": u.ID,
"role_id": u.RoleID,
"company": u.Company,
"dateofbirth": u.DateOfBirth,
"membership": map[string]interface{}{
"id": u.Membership.ID,
"start_date": u.Membership.StartDate,

View File

@@ -24,7 +24,7 @@ func validateUser(sl validator.StructLevel) {
}
// Validate User > 18 years old
if !isSuper && user.DateOfBirth.After(time.Now().AddDate(-18, 0, 0)) {
sl.ReportError(user.DateOfBirth, "DateOfBirth", "date_of_birth", "age", "")
sl.ReportError(user.DateOfBirth, "DateOfBirth", "dateofbirth", "age", "")
}
// validate subscriptionModel
logger.Error.Printf("User: %#v", user)