-
-
e.key == 'Enter' && closeModal()}
- >
-
- {$t('cancel')}
-
@@ -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%;
}
}
diff --git a/frontend/src/lib/components/UserEditForm.svelte b/frontend/src/lib/components/UserEditForm.svelte
index 22fabd2..e25ae53 100644
--- a/frontend/src/lib/components/UserEditForm.svelte
+++ b/frontend/src/lib/components/UserEditForm.svelte
@@ -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')}
/>
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'
+ }
};
diff --git a/frontend/src/lib/utils/helpers.js b/frontend/src/lib/utils/helpers.js
index 9e0e4e1..7c3dc24 100644
--- a/frontend/src/lib/utils/helpers.js
+++ b/frontend/src/lib/utils/helpers.js
@@ -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>, 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
+ });
+ }
+ }
+ }
}
diff --git a/frontend/src/lib/utils/processing.js b/frontend/src/lib/utils/processing.js
new file mode 100644
index 0000000..bfb9cf5
--- /dev/null
+++ b/frontend/src/lib/utils/processing.js
@@ -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 }} Nested object representation of the form data
+ */
+export function formDataToObject(formData) {
+ /** @type { Partial } */
+ 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} */
+ 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}
+ * @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}
+ */
+ 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 } } rawData - The raw form data object
+ * @returns {{ user: Partial }} Processed user data
+ */
+export function processFormData(rawData) {
+ /** @type {{ user: Partial }} */
+ 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;
+}
diff --git a/frontend/src/routes/auth/about/[id]/+page.server.js b/frontend/src/routes/auth/about/[id]/+page.server.js
index f516d0c..3a345e3 100644
--- a/frontend/src/routes/auth/about/[id]/+page.server.js
+++ b/frontend/src/routes/auth/about/[id]/+page.server.js
@@ -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} */
- 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: ''
+ };
+ }
};
diff --git a/frontend/src/routes/auth/about/[id]/+page.svelte b/frontend/src/routes/auth/about/[id]/+page.svelte
index 6fb9630..1479cb7 100644
--- a/frontend/src/routes/auth/about/[id]/+page.svelte
+++ b/frontend/src/routes/auth/about/[id]/+page.svelte
@@ -65,10 +65,10 @@
{user.phone}
{/if}
- {#if user.date_of_birth}
+ {#if user.dateofbirth}
Geburtstag:
- {user.date_of_birth}
+ {user.dateofbirth}
{/if}
{#if user.notes}
diff --git a/frontend/src/routes/auth/admin/users/+page.server.js b/frontend/src/routes/auth/admin/users/+page.server.js
index ba68f25..c6c2d81 100644
--- a/frontend/src/routes/auth/admin/users/+page.server.js
+++ b/frontend/src/routes/auth/admin/users/+page.server.js
@@ -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 }} Nested object representation of the form data
- */
-function formDataToObject(formData) {
- /** @type { Partial } */
- 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} */
- 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}
- * @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}
- */
- 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 } } rawData - The raw form data object
- * @returns {{ user: Partial }} Processed user data
- */
-function processFormData(rawData) {
- /** @type {{ user: Partial }} */
- 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}`);
+ }
};
diff --git a/frontend/src/routes/auth/admin/users/+page.svelte b/frontend/src/routes/auth/admin/users/+page.svelte
index 4d96ff6..98a294b 100644
--- a/frontend/src/routes/auth/admin/users/+page.svelte
+++ b/frontend/src/routes/auth/admin/users/+page.svelte
@@ -58,8 +58,8 @@
on:click={() => setActiveSection('users')}
>
- {users.length}
{$t('users')}
+ {users.length}
@@ -68,8 +68,8 @@
on:click={() => setActiveSection('subscriptions')}
>
- {subscriptions.length}
{$t('subscriptions')}
+ {subscriptions.length}
@@ -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;
}
diff --git a/internal/models/user.go b/internal/models/user.go
index eb246d4..b60fd8e 100644
--- a/internal/models/user.go
+++ b/internal/models/user.go
@@ -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,
diff --git a/internal/validation/user_validation.go b/internal/validation/user_validation.go
index 3c9fbe1..82be122 100644
--- a/internal/validation/user_validation.go
+++ b/internal/validation/user_validation.go
@@ -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)