Compare commits

..

3 Commits

Author SHA1 Message Date
Alex
cce2866b52 frontend cleanup 2025-02-07 22:00:43 +01:00
Alex
3ae1ffd403 backend: fix: err messages 2025-02-07 21:59:55 +01:00
Alex
77619c42bd backend updateUser fix 2025-02-07 21:43:54 +01:00
14 changed files with 56 additions and 73 deletions

View File

@@ -1,5 +1,4 @@
<script> <script>
import { createEventDispatcher } from 'svelte';
import { t } from 'svelte-i18n'; import { t } from 'svelte-i18n';
/** @type {string} */ /** @type {string} */

View File

@@ -1,9 +1,6 @@
<script> <script>
import { quintOut } from 'svelte/easing'; import { quintOut } from 'svelte/easing';
import { t } from 'svelte-i18n';
import { createEventDispatcher } from 'svelte';
const modal = (/** @type {Element} */ node, { duration = 300 } = {}) => { const modal = (/** @type {Element} */ node, { duration = 300 } = {}) => {
const transform = getComputedStyle(node).transform; const transform = getComputedStyle(node).transform;
@@ -19,11 +16,6 @@
} }
}; };
}; };
const dispatch = createEventDispatcher();
function closeModal() {
dispatch('close', {});
}
</script> </script>
<div class="modal-background"> <div class="modal-background">
@@ -59,42 +51,11 @@
box-shadow: 0 4px 20px rgba(17, 17, 27, 0.5); /* var(--crust) with opacity */ box-shadow: 0 4px 20px rgba(17, 17, 27, 0.5); /* var(--crust) with opacity */
transform: translate(-50%, -50%); transform: translate(-50%, -50%);
} }
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border-width: 0;
}
@media (max-width: 990px) { @media (max-width: 990px) {
.modal { .modal {
width: 90%; width: 90%;
} }
} }
.modal-close {
border: none;
padding: 1rem;
cursor: pointer;
position: absolute;
right: 0;
top: 0;
}
.modal-close svg {
display: block;
margin-left: auto;
margin-right: auto;
transition: all 0.3s ease-in-out;
fill: var(--blue); /* Using Catppuccin blue */
}
.modal-close:hover svg {
fill: var(--red); /* Using Catppuccin red */
transform: scale(1.2);
}
.modal .container { .modal .container {
max-height: 90vh; max-height: 90vh;
overflow-y: auto; overflow-y: auto;

View File

@@ -80,7 +80,7 @@
} }
} }
$: isNewUser = user === null; // $: isNewUser = user === null;
$: isLoading = user === undefined; $: isLoading = user === undefined;
$: { $: {
@@ -135,7 +135,8 @@
label: sub?.name ?? '' label: sub?.name ?? ''
})); }));
$: selectedSubscriptionModel = $: selectedSubscriptionModel =
subscriptions.find((sub) => sub?.id === localUser.membership?.subscription_model.id) || null; subscriptions.find((sub) => sub?.name === localUser.membership?.subscription_model.name) ||
null;
/** /**
* creates groups of categories depending on the first letter * creates groups of categories depending on the first letter
@@ -146,7 +147,7 @@
return Object.entries(categories) return Object.entries(categories)
.sort((a, b) => a[1].category.localeCompare(b[1].category)) .sort((a, b) => a[1].category.localeCompare(b[1].category))
.reduce( .reduce(
(/** @type {Object.<string, App.Locals['licence_categories']>} */ acc, [_, category]) => { (/** @type {Object.<string, App.Locals['licence_categories']>} */ acc, [, category]) => {
const firstLetter = category.category[0]; const firstLetter = category.category[0];
if (!acc[firstLetter]) { if (!acc[firstLetter]) {
acc[firstLetter] = []; acc[firstLetter] = [];
@@ -167,12 +168,12 @@
} }
/** @type {import('../../routes/auth/about/[id]/$types').SubmitFunction} */ /** @type {import('../../routes/auth/about/[id]/$types').SubmitFunction} */
const handleUpdate = async ({ formData, action, cancel }) => { const handleUpdate = async () => {
isUpdating = true; isUpdating = true;
return async ({ result }) => { return async ({ result }) => {
isUpdating = false; isUpdating = false;
if (result.type === 'success' || result.type === 'redirect') { if (result.type === 'success' || result.type === 'redirect') {
close(); dispatch('close');
} else { } else {
document.querySelector('.modal .container')?.scrollTo({ top: 0, behavior: 'smooth' }); document.querySelector('.modal .container')?.scrollTo({ top: 0, behavior: 'smooth' });
} }
@@ -371,7 +372,7 @@
<div class="licence-categories"> <div class="licence-categories">
<h3>{$t('licence_categories')}</h3> <h3>{$t('licence_categories')}</h3>
<div class="checkbox-grid"> <div class="checkbox-grid">
{#each Object.entries(groupedCategories) as [group, categories], groupIndex} {#each Object.entries(groupedCategories) as [, categories], groupIndex}
{#if groupIndex > 0} {#if groupIndex > 0}
<div class="category-break"></div> <div class="category-break"></div>
{/if} {/if}
@@ -417,11 +418,11 @@
<div class="subscription-column"> <div class="subscription-column">
<p> <p>
<strong>{$t('monthly_fee')}:</strong> <strong>{$t('monthly_fee')}:</strong>
{selectedSubscriptionModel?.monthly_fee || '-'} {selectedSubscriptionModel?.monthly_fee || '-'}
</p> </p>
<p> <p>
<strong>{$t('hourly_rate')}:</strong> <strong>{$t('hourly_rate')}:</strong>
{selectedSubscriptionModel?.hourly_rate || '-'} {selectedSubscriptionModel?.hourly_rate || '-'}
</p> </p>
{#if selectedSubscriptionModel?.included_hours_per_year} {#if selectedSubscriptionModel?.included_hours_per_year}
<p> <p>

View File

@@ -32,10 +32,10 @@ export const actions = {
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; const isCreating = !processedData.user.id || processedData.user.id === 0;
console.log('Is updating: ', isCreating); console.log('Is creating: ', isCreating);
console.dir(formData); // console.dir(formData);
console.dir(processedData.user.membership);
const apiURL = `${BASE_API_URI}/backend/users/update/`; const apiURL = `${BASE_API_URI}/backend/users/update/`;
/** @type {RequestInit} */ /** @type {RequestInit} */

View File

@@ -86,7 +86,14 @@
{#if showModal} {#if showModal}
<Modal on:close={close}> <Modal on:close={close}>
<UserEditForm {form} {user} {subscriptions} {licence_categories} on:cancel={close} /> <UserEditForm
{form}
{user}
{subscriptions}
{licence_categories}
on:close={close}
on:cancel={close}
/>
</Modal> </Modal>
{/if} {/if}

View File

@@ -8,7 +8,7 @@ import { fail, redirect } from '@sveltejs/kit';
import { formDataToObject, processFormData } from '$lib/utils/processing'; import { formDataToObject, processFormData } from '$lib/utils/processing';
/** @type {import('./$types').PageServerLoad} */ /** @type {import('./$types').PageServerLoad} */
export async function load({ locals, params }) { export async function load({ locals }) {
// redirect user if not logged in // redirect user if not logged in
if (!locals.user) { if (!locals.user) {
throw redirect(302, `/auth/login?next=/auth/users`); throw redirect(302, `/auth/login?next=/auth/users`);

View File

@@ -7,13 +7,7 @@
/** @type {import('./$types').ActionData} */ /** @type {import('./$types').ActionData} */
export let form; export let form;
$: ({ $: ({ users = [], licence_categories = [], subscriptions = [], payments = [] } = $page.data);
user,
users = [],
licence_categories = [],
subscriptions = [],
payments = []
} = $page.data);
let activeSection = 'users'; let activeSection = 'users';
/** @type{App.Locals['user'] | null} */ /** @type{App.Locals['user'] | null} */

View File

@@ -84,14 +84,6 @@ func (uc *UserController) UpdateHandler(c *gin.Context) {
return return
} }
// Validate subscription model
selectedModel, err := uc.MembershipService.GetModelByName(&user.Membership.SubscriptionModel.Name)
if err != nil {
utils.RespondWithError(c, err, "Error in UpdateHandler", http.StatusNotFound, "subscription_model", "server.validation.subscription_model_not_found")
return
}
user.Membership.SubscriptionModel = *selectedModel
updatedUser, err := uc.Service.UpdateUser(&user) updatedUser, err := uc.Service.UpdateUser(&user)
if err != nil { if err != nil {
utils.HandleUpdateError(c, err) utils.HandleUpdateError(c, err)

View File

@@ -51,9 +51,11 @@ func (ur *UserRepository) UpdateUser(user *models.User) (*models.User, error) {
err := database.DB.Transaction(func(tx *gorm.DB) error { err := database.DB.Transaction(func(tx *gorm.DB) error {
// Check if the user exists in the database // Check if the user exists in the database
var existingUser models.User var existingUser models.User
if err := tx.Preload("Licence").
Preload("Licence.Categories"). if err := tx.Preload(clause.Associations).
Preload("Membership"). Preload("Membership").
Preload("Membership.SubscriptionModel").
Preload("Licence.Categories").
First(&existingUser, user.ID).Error; err != nil { First(&existingUser, user.ID).Error; err != nil {
return err return err
} }
@@ -109,6 +111,7 @@ func GetUserByID(userID *uint) (*models.User, error) {
var user models.User var user models.User
result := database.DB. result := database.DB.
Preload(clause.Associations). Preload(clause.Associations).
Preload("Membership").
Preload("Membership.SubscriptionModel"). Preload("Membership.SubscriptionModel").
Preload("Licence.Categories"). Preload("Licence.Categories").
First(&user, userID) First(&user, userID)

View File

@@ -41,6 +41,29 @@ func (service *UserService) UpdateUser(user *models.User) (*models.User, error)
setPassword(user.Password, user) setPassword(user.Password, user)
} }
// Validate subscription model
selectedModel, err := repositories.GetModelByName(&user.Membership.SubscriptionModel.Name)
if err != nil {
return nil, errors.ErrSubscriptionNotFound
}
user.Membership.SubscriptionModel = *selectedModel
user.Membership.SubscriptionModelID = selectedModel.ID
existingUser, err := service.GetUserByID(user.ID)
if err != nil {
return nil, err
}
user.Membership.ID = existingUser.Membership.ID
user.MembershipID = existingUser.MembershipID
if existingUser.Licence != nil {
user.Licence.ID = existingUser.Licence.ID
}
user.LicenceID = existingUser.LicenceID
user.BankAccount.ID = existingUser.BankAccount.ID
user.BankAccountID = existingUser.BankAccountID
// if user.Licence.Status == 0 { // if user.Licence.Status == 0 {
// // This is a new drivers licence // // This is a new drivers licence
// user.Licence.Status = constants.UnverifiedStatus // user.Licence.Status = constants.UnverifiedStatus

View File

@@ -42,6 +42,8 @@ func HandleUpdateError(c *gin.Context, err error) {
RespondWithError(c, err, "Error while updating user", http.StatusNotFound, "user", "server.validation.user_not_found") RespondWithError(c, err, "Error while updating user", http.StatusNotFound, "user", "server.validation.user_not_found")
case errors.ErrInvalidUserData: case errors.ErrInvalidUserData:
RespondWithError(c, err, "Error while updating user", http.StatusBadRequest, "user", "server.validation.invalid_user_data") RespondWithError(c, err, "Error while updating user", http.StatusBadRequest, "user", "server.validation.invalid_user_data")
case errors.ErrSubscriptionNotFound:
RespondWithError(c, err, "Error while updating user", http.StatusBadRequest, "subscription", "server.validation.subscription_data")
default: default:
RespondWithError(c, err, "Error while updating user", http.StatusInternalServerError, "user", "server.error.internal_server_error") RespondWithError(c, err, "Error while updating user", http.StatusInternalServerError, "user", "server.error.internal_server_error")
} }

View File

@@ -15,12 +15,12 @@ func validateMembership(sl validator.StructLevel) {
switch membership.SubscriptionModel.RequiredMembershipField { switch membership.SubscriptionModel.RequiredMembershipField {
case "ParentMembershipID": case "ParentMembershipID":
if err := CheckParentMembershipID(membership); err != nil { if err := CheckParentMembershipID(membership); err != nil {
logger.Error.Printf(err.Error()) logger.Error.Printf("Error ParentMembershipValidation: %v", err.Error())
sl.ReportError(membership.ParentMembershipID, membership.SubscriptionModel.RequiredMembershipField, sl.ReportError(membership.ParentMembershipID, membership.SubscriptionModel.RequiredMembershipField,
"RequiredMembershipField", "invalid", "") "RequiredMembershipField", "invalid", "")
} }
default: default:
logger.Error.Printf(errors.ErrInvalidValue.Error()) logger.Error.Printf("Error no matching RequiredMembershipField: %v", errors.ErrInvalidValue.Error())
sl.ReportError(membership.ParentMembershipID, membership.SubscriptionModel.RequiredMembershipField, sl.ReportError(membership.ParentMembershipID, membership.SubscriptionModel.RequiredMembershipField,
"RequiredMembershipField", "not_implemented", "") "RequiredMembershipField", "not_implemented", "")
} }

View File

@@ -27,7 +27,7 @@ func validateUser(sl validator.StructLevel) {
sl.ReportError(user.DateOfBirth, "DateOfBirth", "dateofbirth", "age", "") sl.ReportError(user.DateOfBirth, "DateOfBirth", "dateofbirth", "age", "")
} }
// validate subscriptionModel // validate subscriptionModel
logger.Error.Printf("User: %#v", user) logger.Error.Printf("User SubscriptionModel.Name: %#v", user.Membership.SubscriptionModel.Name)
if user.Membership.SubscriptionModel.Name == "" { if user.Membership.SubscriptionModel.Name == "" {
sl.ReportError(user.Membership.SubscriptionModel.Name, "SubscriptionModel.Name", "name", "required", "") sl.ReportError(user.Membership.SubscriptionModel.Name, "SubscriptionModel.Name", "name", "required", "")
} else { } else {

View File

@@ -43,6 +43,7 @@ var (
ErrNotAuthorized = errors.New("not authorized") ErrNotAuthorized = errors.New("not authorized")
ValErrParentIDNotSet = errors.New("Parent Membership ID not provided") ValErrParentIDNotSet = errors.New("Parent Membership ID not provided")
ValErrParentIDNotFound = errors.New("Parent Membership ID not found") ValErrParentIDNotFound = errors.New("Parent Membership ID not found")
ErrSubscriptionNotFound = errors.New("Subscription Model not found")
) )
var Responses = struct { var Responses = struct {