From 87f08dd3be046cf87eeffd3a98d28fe2f4a4d93a Mon Sep 17 00:00:00 2001 From: Alex <$(pass /github/email)> Date: Mon, 24 Mar 2025 18:00:57 +0100 Subject: [PATCH] subscription_model -> subscription --- frontend/src/app.d.ts | 2 +- .../src/lib/components/UserEditForm.svelte | 102 +++++++++++------- frontend/src/lib/locales/de.js | 11 +- frontend/src/lib/locales/en.js | 6 +- frontend/src/lib/utils/constants.js | 3 +- frontend/src/lib/utils/defaults.js | 42 ++++---- frontend/src/lib/utils/processing.js | 74 ++++--------- .../routes/auth/admin/users/+page.server.js | 9 +- .../src/routes/auth/admin/users/+page.svelte | 51 ++++----- go-backend/internal/models/membership.go | 2 +- go-backend/internal/models/user.go | 2 +- go-backend/pkg/errors/errors.go | 4 +- 12 files changed, 145 insertions(+), 163 deletions(-) diff --git a/frontend/src/app.d.ts b/frontend/src/app.d.ts index 326f5ce..f6fa3c9 100644 --- a/frontend/src/app.d.ts +++ b/frontend/src/app.d.ts @@ -17,7 +17,7 @@ interface Membership { start_date: string | ''; end_date: string | ''; parent_member_id: number | -1; - subscription_model: Subscription; + subscription: Subscription; } interface BankAccount { diff --git a/frontend/src/lib/components/UserEditForm.svelte b/frontend/src/lib/components/UserEditForm.svelte index a428a74..9b1f330 100644 --- a/frontend/src/lib/components/UserEditForm.svelte +++ b/frontend/src/lib/components/UserEditForm.svelte @@ -11,12 +11,14 @@ /** @type {import('../../routes/auth/about/[id]/$types').ActionData} */ export let form; - /** @type {App.Locals['subscriptions']}*/ + /** @type {App.Locals['subscriptions'] | null}*/ export let subscriptions; /** @type {App.Locals['user']} */ export let user; + export let submit_form = true; + // Ensure licence is initialized before passing to child $: if (user && !user.licence) { user.licence = defaultLicence(); @@ -30,7 +32,7 @@ // $: isNewUser = user === null; $: isLoading = user === undefined; - /** @type {App.Locals['licence_categories']} */ + /** @type {App.Locals['licence_categories'] | null} */ export let licence_categories; const userStatusOptions = [ @@ -60,9 +62,13 @@ ]; const dispatch = createEventDispatcher(); - const TABS = hasPrivilige(user, PERMISSIONS.Member) - ? ['profile', 'licence', 'membership', 'bankaccount'] - : ['profile', 'bankaccount', 'membership']; + const TABS = [ + 'profile', + 'bankaccount', + ...(hasPrivilige(user, PERMISSIONS.Member) ? 'membership' : []), + ...(user.licence ? 'licence' : []) + ]; + let activeTab = TABS[0]; let isUpdating = false, @@ -70,14 +76,16 @@ confirm_password = ''; /** @type {Object.} */ - $: groupedCategories = groupCategories(licence_categories); - $: subscriptionModelOptions = subscriptions.map((sub) => ({ - value: sub?.name ?? '', - label: sub?.name ?? '' - })); - $: selectedSubscriptionModel = - subscriptions.find((sub) => sub?.name === user.membership?.subscription_model.name) || null; - + $: groupedCategories = licence_categories ? groupCategories(licence_categories) : {}; + $: subscriptionModelOptions = subscriptions + ? subscriptions.map((sub) => ({ + value: sub?.name ?? '', + label: sub?.name ?? '' + })) + : []; + $: selectedSubscriptionModel = subscriptions + ? subscriptions.find((sub) => sub?.name === user.membership?.subscription.name) || null + : null; /** * creates groups of categories depending on the first letter * @param {App.Locals['licence_categories']} categories - the categories to sort and group @@ -99,17 +107,25 @@ ); } - /** @type {import('../../routes/auth/about/[id]/$types').SubmitFunction} */ - const handleUpdate = async () => { + /** @type {import('@sveltejs/kit').SubmitFunction} */ + const handleUpdate = ({ cancel }) => { + if (!submit_form) { + cancel(); + dispatch('close'); + return; + } isUpdating = true; + return async ({ result }) => { isUpdating = false; + if (result.type === 'success' || result.type === 'redirect') { dispatch('close'); } else { document.querySelector('.modal .container')?.scrollTo({ top: 0, behavior: 'smooth' }); } - await applyAction(result); + console.log('submitting'); + return submit_form ? await applyAction(result) : undefined; }; }; @@ -117,7 +133,18 @@ {#if isLoading} {:else if user} -
+ { + if (!submit_form) { + e.preventDefault(); + dispatch('close'); + } + }} + >

{user.id ? $t('user.edit') : $t('user.create')} @@ -229,16 +256,14 @@ bind:value={user.phone} placeholder={$t('placeholder.phone')} /> - {#if hasPrivilige(user, PERMISSIONS.Member)} - - {/if} + - {#if hasPrivilige(user, PERMISSIONS.Member)} + {#if hasPrivilige(user, PERMISSIONS.Member) && user.licence}
{/if} -
+
@@ -363,22 +391,22 @@ {#if hasPrivilige(user, PERMISSIONS.Member)}

- {$t('subscription.monthly_fee')}: + {$t('subscriptions.monthly_fee')}: {selectedSubscriptionModel?.monthly_fee || '-'} €

- {$t('subscription.hourly_rate')}: + {$t('subscriptions.hourly_rate')}: {selectedSubscriptionModel?.hourly_rate || '-'} €

{#if selectedSubscriptionModel?.included_hours_per_year}

- {$t('subscription.included_hours_per_year')}: + {$t('subscriptions.included_hours_per_year')}: {selectedSubscriptionModel?.included_hours_per_year}

{/if} {#if selectedSubscriptionModel?.included_hours_per_month}

- {$t('subscription.included_hours_per_month')}: + {$t('subscriptions.included_hours_per_month')}: {selectedSubscriptionModel?.included_hours_per_month}

{/if} @@ -391,7 +419,7 @@

{#if selectedSubscriptionModel?.conditions}

- {$t('subscription.conditions')}: + {$t('subscriptions.conditions')}: {selectedSubscriptionModel?.conditions}

{/if} diff --git a/frontend/src/lib/locales/de.js b/frontend/src/lib/locales/de.js index 53c57cd..5988863 100644 --- a/frontend/src/lib/locales/de.js +++ b/frontend/src/lib/locales/de.js @@ -7,11 +7,12 @@ export default { 5: 'Passiv' }, userRole: { + '-5': 'Unfallgegner', 0: 'Sponsor', 1: 'Mitglied', 2: 'Betrachter', 4: 'Bearbeiter', - 8: 'Adm/endinistrator' + 8: 'Administrator' }, placeholder: { car_name: 'Hat das Fahrzeug einen Namen?', @@ -75,7 +76,7 @@ export default { validation: { invalid: 'ungültig', invalid_user_id: 'Nutzer ID ungültig', - invalid_subscription_model: 'Model nicht gefunden', + invalid_subscription: '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', @@ -141,7 +142,7 @@ export default { role: 'Nutzerrolle', supporter: 'Sponsor' }, - subscription: { + subscriptions: { name: 'Modellname', edit: 'Modell bearbeiten', create: 'Modell erstellen', @@ -176,6 +177,8 @@ export default { user_deletion: 'Soll der Nutzer {firstname} {lastname} wirklich gelöscht werden?', subscription_deletion: 'Soll das Tarifmodell {name} wirklich gelöscht werden?', car_deletion: 'Soll das Fahrzeug {name} wirklich gelöscht werden?', + insurance_deletion: 'Soll die Versicherung {name} wirklich gelöscht werden?', + damage_deletion: 'Soll der Schaden {name} wirklich gelöscht werden?', backend_access: 'Soll {firstname} {lastname} Backend Zugriff gewährt werden?' }, cancel: 'Abbrechen', @@ -192,7 +195,7 @@ export default { supporter: 'Sponsoren', mandate_date_signed: 'Mandatserteilungsdatum', licence_categories: 'Führerscheinklassen', - subscription_model: 'Mitgliedschatfsmodell', + subscription: 'Mitgliedschatfsmodell', licence: 'Führerschein', licence_number: 'Führerscheinnummer', insurance: 'Versicherung', diff --git a/frontend/src/lib/locales/en.js b/frontend/src/lib/locales/en.js index dce525f..872c5c9 100644 --- a/frontend/src/lib/locales/en.js +++ b/frontend/src/lib/locales/en.js @@ -69,7 +69,7 @@ export default { validation: { invalid: 'Invalid', invalid_user_id: 'Invalid user ID', - invalid_subscription_model: 'Model not found', + invalid_subscription: 'Model not found', user_not_found: '{field} could not be found', invalid_user_data: 'Invalid user data', user_not_found_or_wrong_password: 'Does not exist or wrong password', @@ -128,7 +128,7 @@ export default { role: 'User Role', supporter: 'Sponsor' }, - subscription: { + subscriptions: { name: 'Model Name', edit: 'Edit Model', create: 'Create Model', @@ -160,7 +160,7 @@ export default { supporter: 'Sponsors', mandate_date_signed: 'Mandate Signing Date', licence_categories: 'Driver’s licence Categories', - subscription_model: 'Membership Model', + subscription: 'Membership Model', licence: 'Driver’s licence', licence_number: 'Driver’s licence Number', issued_date: 'Issue Date', diff --git a/frontend/src/lib/utils/constants.js b/frontend/src/lib/utils/constants.js index 7a454bf..6fd7acc 100644 --- a/frontend/src/lib/utils/constants.js +++ b/frontend/src/lib/utils/constants.js @@ -11,4 +11,5 @@ export const PERMISSIONS = { Super: 8 }; -export const SUPPORTER_SUBSCRIPTION_MODEL_NAME = 'Keins'; +export const SUPPORTER_SUBSCRIPTION_NAME = 'Keins'; +export const OPPONENT_SUBSCRIPTION_NAME = 'Keins'; diff --git a/frontend/src/lib/utils/defaults.js b/frontend/src/lib/utils/defaults.js index 7ba14c1..2882132 100644 --- a/frontend/src/lib/utils/defaults.js +++ b/frontend/src/lib/utils/defaults.js @@ -1,6 +1,6 @@ // src/lib/utils/defaults.js -import { SUPPORTER_SUBSCRIPTION_MODEL_NAME } from './constants'; +import { OPPONENT_SUBSCRIPTION_NAME, SUPPORTER_SUBSCRIPTION_NAME } from './constants'; /** * @returns {App.Types['subscription']} @@ -28,7 +28,7 @@ export function defaultMembership() { start_date: '', end_date: '', parent_member_id: 0, - subscription_model: defaultSubscription() + subscription: defaultSubscription() }; } @@ -93,31 +93,25 @@ export function defaultUser() { * @returns {App.Locals['user']} */ export function defaultSupporter() { - let supporter = { - id: 0, - email: '', - first_name: '', - last_name: '', - password: '', - phone: '', - address: '', - zip_code: '', - city: '', - company: '', - dateofbirth: '', - notes: '', - profile_picture: '', - payment_status: 0, - status: 5, - role_id: 0, - membership: defaultMembership(), - licence: defaultLicence(), - bank_account: defaultBankAccount() - }; - supporter.membership.subscription_model.name = SUPPORTER_SUBSCRIPTION_MODEL_NAME; + let supporter = defaultUser(); + supporter.status = 5; + supporter.role_id = 0; + supporter.licence = null; + supporter.membership.subscription.name = SUPPORTER_SUBSCRIPTION_NAME; return supporter; } +/** + * @returns {App.Locals['user']} + */ +export function defaultOpponent() { + let opponent = defaultUser(); + opponent.status = 5; + opponent.role_id = -1; + opponent.licence = null; + opponent.membership.subscription.name = OPPONENT_SUBSCRIPTION_NAME; + return opponent; +} /** * @returns {App.Types['location']} */ diff --git a/frontend/src/lib/utils/processing.js b/frontend/src/lib/utils/processing.js index 2769e80..8eed0e0 100644 --- a/frontend/src/lib/utils/processing.js +++ b/frontend/src/lib/utils/processing.js @@ -62,44 +62,16 @@ export function formDataToObject(formData) { * @param {{ object: Partial, confirm_password: string} } rawData - The raw form data object * @returns {{ user: Partial }} Processed user data */ -export function processUserFormData(rawData) { - /** @type {{ user: Partial }} */ - let processedData = { - user: { - id: Number(rawData.object.id) || 0, - status: Number(rawData.object.status), - role_id: Number(rawData.object.role_id), - first_name: String(rawData.object.first_name), - last_name: String(rawData.object.last_name), - email: String(rawData.object.email), - phone: String(rawData.object.phone || ''), - company: String(rawData.object.company || ''), - dateofbirth: toRFC3339(String(rawData.object.dateofbirth || '')), - address: String(rawData.object.address || ''), - zip_code: String(rawData.object.zip_code || ''), - city: String(rawData.object.city || ''), - notes: String(rawData.object.notes || ''), - profile_picture: String(rawData.object.profile_picture || ''), - - membership: { - id: Number(rawData.object.membership?.id) || 0, - status: Number(rawData.object.membership?.status), - start_date: toRFC3339(String(rawData.object.membership?.start_date || '')), - end_date: toRFC3339(String(rawData.object.membership?.end_date || '')), - parent_member_id: Number(rawData.object.membership?.parent_member_id) || 0, - subscription_model: { - id: Number(rawData.object.membership?.subscription_model?.id) || 0, - name: String(rawData.object.membership?.subscription_model?.name) || '', - details: String(rawData.object.membership?.subscription_model?.details) || '', - conditions: String(rawData.object.membership?.subscription_model?.conditions) || '', - hourly_rate: Number(rawData.object.membership?.subscription_model?.hourly_rate) || 0, - monthly_fee: Number(rawData.object.membership?.subscription_model?.monthly_fee) || 0, - included_hours_per_month: - Number(rawData.object.membership?.subscription_model?.included_hours_per_month) || 0, - included_hours_per_year: - Number(rawData.object.membership?.subscription_model?.included_hours_per_year) || 0 - } - }, +export function processMembershipFormData(membership) { + return { + id: Number(membership.id) || 0, + status: Number(membership.status), + start_date: toRFC3339(String(membership.start_date || '')), + end_date: toRFC3339(String(membership.end_date || '')), + parent_member_id: Number(membership.parent_member_id) || 0, + subscription: processSubscriptionFormData(membership.subscription) + }; +} licence: { id: Number(rawData.object.licence?.id) || 0, @@ -143,22 +115,20 @@ export function processUserFormData(rawData) { /** * Processes the raw form data into the expected subscription data structure - * @param {{ object: Partial, confirm_password: string }} rawData - The raw form data object - * @returns {{ subscription: Partial }} Processed user data + * @param {Partial} subscription - The raw form data object + * @returns {App.Types['subscription']} Processed user data */ -export function processSubscriptionFormData(rawData) { - /** @type {{ subscription: Partial }} */ +export function processSubscriptionFormData(subscription) { + /** @type {Partial} */ let processedData = { - subscription: { - id: Number(rawData.object.id) || 0, - name: String(rawData.object.name) || '', - details: String(rawData.object.details) || '', - conditions: String(rawData.object.conditions) || '', - hourly_rate: Number(rawData.object.hourly_rate) || 0, - monthly_fee: Number(rawData.object.monthly_fee) || 0, - included_hours_per_month: Number(rawData.object.included_hours_per_month) || 0, - included_hours_per_year: Number(rawData.object.included_hours_per_year) || 0 - } + id: Number(subscription.id) || 0, + name: String(subscription.name) || '', + details: String(subscription.details) || '', + conditions: String(subscription.conditions) || '', + hourly_rate: Number(subscription.hourly_rate) || 0, + monthly_fee: Number(subscription.monthly_fee) || 0, + included_hours_per_month: Number(subscription.included_hours_per_month) || 0, + included_hours_per_year: Number(subscription.included_hours_per_year) || 0 }; const clean = JSON.parse(JSON.stringify(processedData), (key, value) => value !== null && value !== '' ? value : undefined diff --git a/frontend/src/routes/auth/admin/users/+page.server.js b/frontend/src/routes/auth/admin/users/+page.server.js index ab02e9d..cad0594 100644 --- a/frontend/src/routes/auth/admin/users/+page.server.js +++ b/frontend/src/routes/auth/admin/users/+page.server.js @@ -87,10 +87,11 @@ export const actions = { updateSubscription: async ({ request, fetch, cookies }) => { let formData = await request.formData(); - const rawData = formDataToObject(formData); - const processedData = processSubscriptionFormData(rawData); + const rawFormData = formDataToObject(formData); + const rawSubscription = /** @type {Partial} */ (rawFormData.object); + const subscription = processSubscriptionFormData(rawSubscription); - const isCreating = !processedData.subscription.id || processedData.subscription.id === 0; + const isCreating = !subscription.id || subscription.id === 0; console.log('Is creating: ', isCreating); const apiURL = `${BASE_API_URI}/auth/subscriptions`; @@ -102,7 +103,7 @@ export const actions = { 'Content-Type': 'application/json', Cookie: `jwt=${cookies.get('jwt')}` }, - body: JSON.stringify(processedData.subscription) + body: JSON.stringify(subscription) }; const res = await fetch(apiURL, requestOptions); diff --git a/frontend/src/routes/auth/admin/users/+page.svelte b/frontend/src/routes/auth/admin/users/+page.svelte index a4800e9..b03a5ad 100644 --- a/frontend/src/routes/auth/admin/users/+page.svelte +++ b/frontend/src/routes/auth/admin/users/+page.svelte @@ -83,9 +83,7 @@ user.licence?.number?.toLowerCase() ].some((field) => field?.includes(term)); - const subscriptionMatch = user.membership?.subscription_model?.name - ?.toLowerCase() - .includes(term); + const subscriptionMatch = user.membership?.subscription?.name?.toLowerCase().includes(term); const licenceCategoryMatch = user.licence?.categories?.some((cat) => cat.category.toLowerCase().includes(term) @@ -277,8 +275,8 @@ {user.email} - {$t('subscription.subscription')} - {user.membership?.subscription_model?.name} + {$t('subscriptions.subscription')} + {user.membership?.subscription?.name} {$t('status')} @@ -462,7 +460,7 @@ {members.filter( (/** @type{App.Locals['user']}*/ user) => - user.membership?.subscription_model?.name === subscription.name + user.membership?.subscription?.name === subscription.name ).length} @@ -514,33 +512,20 @@ {$t('edit')} - {#if !members.some(/** @param{App.Locals['user']} user */ (user) => user.membership?.subscription_model?.id === subscription.id)} - { - return async ({ result }) => { - if (result.type === 'success' || result.type === 'redirect') { - await applyAction(result); - } else { - document - .querySelector('.accordion-content') - ?.scrollTo({ top: 0, behavior: 'smooth' }); - await applyAction(result); - } - }; - }} - on:submit|preventDefault={(/** @type {SubmitEvent} */ e) => { - if ( - !confirm( - $t('dialog.subscription_deletion', { - values: { - name: subscription.name || '' - } - }) - ) - ) { - e.preventDefault(); // Cancel form submission if user declines + {/if} + {#if !members.some(/** @param{App.Locals['user']} user */ (user) => user.membership?.subscription?.id === subscription.id)} + { + return async ({ result }) => { + if (result.type === 'success' || result.type === 'redirect') { + await applyAction(result); + } else { + document + .querySelector('.accordion-content') + ?.scrollTo({ top: 0, behavior: 'smooth' }); + await applyAction(result); } }} > diff --git a/go-backend/internal/models/membership.go b/go-backend/internal/models/membership.go index 81f9cec..25d4868 100644 --- a/go-backend/internal/models/membership.go +++ b/go-backend/internal/models/membership.go @@ -15,7 +15,7 @@ type Membership struct { StartDate time.Time `json:"start_date"` EndDate time.Time `json:"end_date"` Status int8 `json:"status" binding:"number,safe_content"` - SubscriptionModel SubscriptionModel `gorm:"foreignKey:SubscriptionModelID" json:"subscription_model"` + SubscriptionModel SubscriptionModel `gorm:"foreignKey:SubscriptionModelID" json:"subscription"` SubscriptionModelID uint `json:"subsription_model_id"` ParentMembershipID uint `json:"parent_member_id" binding:"omitempty,omitnil,number"` } diff --git a/go-backend/internal/models/user.go b/go-backend/internal/models/user.go index 304de0d..498cdf8 100644 --- a/go-backend/internal/models/user.go +++ b/go-backend/internal/models/user.go @@ -441,7 +441,7 @@ func (u *User) Safe() map[string]interface{} { "start_date": u.Membership.StartDate, "end_date": u.Membership.EndDate, "status": u.Membership.Status, - "subscription_model": map[string]interface{}{ + "subscription": map[string]interface{}{ "id": u.Membership.SubscriptionModel.ID, "name": u.Membership.SubscriptionModel.Name, "details": u.Membership.SubscriptionModel.Details, diff --git a/go-backend/pkg/errors/errors.go b/go-backend/pkg/errors/errors.go index bda9ed3..8581ac2 100644 --- a/go-backend/pkg/errors/errors.go +++ b/go-backend/pkg/errors/errors.go @@ -66,7 +66,7 @@ var Responses = struct { InternalServerError: "server.error.internal_server_error", InvalidJSON: "server.error.invalid_json", InvalidUserID: "server.validation.invalid_user_id", - InvalidSubscriptionModel: "server.validation.invalid_subscription_model", + InvalidSubscriptionModel: "server.validation.invalid_subscription", Unauthorized: "server.error.unauthorized", UserNotFoundWrongPassword: "server.validation.user_not_found_or_wrong_password", JwtGenerationFailed: "server.error.jwt_generation_failed", @@ -82,7 +82,7 @@ var Responses = struct { Fields: ValidationFields{ General: "server.general", ParentMembershipID: "parent_membership_id", - SubscriptionModel: "subscription_model", + SubscriptionModel: "subscription", Login: "user.login", Email: "user.email", User: "user.user",