This commit is contained in:
Alex
2025-01-29 16:02:37 +01:00
parent f68ca9abc5
commit c2d5188765
8 changed files with 1632 additions and 1535 deletions

2
frontend/.gitignore vendored
View File

@@ -35,3 +35,5 @@ Thumbs.db
!vite.config.js # Vite configuration !vite.config.js # Vite configuration
vite.config.js.timestamp-* vite.config.js.timestamp-*
vite.config.ts.timestamp-* vite.config.ts.timestamp-*
!src/**

View File

@@ -1,18 +1,18 @@
<script> <script>
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from 'svelte';
import { t } from "svelte-i18n"; import { t } from 'svelte-i18n';
/** @type {string} */ /** @type {string} */
export let name; export let name;
/** @type {string} */ /** @type {string} */
export let type = "text"; export let type = 'text';
/** @type {string|Number|null} */ /** @type {string|Number|null} */
export let value; export let value;
/** @type {string} */ /** @type {string} */
export let placeholder = ""; export let placeholder = '';
/** @type {Number} */ /** @type {Number} */
export let rows = 4; export let rows = 4;
@@ -24,10 +24,10 @@
export let required = false; export let required = false;
/** @type {string} */ /** @type {string} */
export let label = ""; export let label = '';
/** @type {string} */ /** @type {string} */
export let otherPasswordValue = ""; export let otherPasswordValue = '';
/** @type {boolean} */ /** @type {boolean} */
export let toUpperCase = false; export let toUpperCase = false;
@@ -62,66 +62,57 @@
* @returns {string|null} The error message or null if valid * @returns {string|null} The error message or null if valid
*/ */
function validateField(name, value, required) { function validateField(name, value, required) {
if ( if (value === null || (typeof value === 'string' && !value.trim() && !required)) return null;
value === null ||
(typeof value === "string" && !value.trim() && !required)
)
return null;
switch (name) { switch (name) {
case "membership_start_date": case 'membership_start_date':
return typeof value === "string" && value.trim() return typeof value === 'string' && value.trim() ? null : $t('validation.date');
case 'email':
return typeof value === 'string' && /^\S+@\S+\.\S+$/.test(value)
? null ? null
: $t("validation.date"); : $t('validation.email');
case "email": case 'password':
return typeof value === "string" && /^\S+@\S+\.\S+$/.test(value) case 'password2':
? null if (typeof value === 'string' && value.length < 8) {
: $t("validation.email"); return $t('validation.password');
case "password":
case "password2":
if (typeof value === "string" && value.length < 8) {
return $t("validation.password");
} }
if (otherPasswordValue && value !== otherPasswordValue) { if (otherPasswordValue && value !== otherPasswordValue) {
return $t("validation.password_match"); return $t('validation.password_match');
} }
return null; return null;
case "phone": case 'phone':
return typeof value === "string" && /^\+?[0-9\s()-]{7,}$/.test(value) return typeof value === 'string' && /^\+?[0-9\s()-]{7,}$/.test(value)
? null ? null
: $t("validation.phone"); : $t('validation.phone');
case "zip_code": case 'zip_code':
return typeof value === "string" && /^\d{5}$/.test(value) return typeof value === 'string' && /^\d{5}$/.test(value)
? null ? null
: $t("validation.zip_code"); : $t('validation.zip_code');
case "iban": case 'iban':
return typeof value === "string" && return typeof value === 'string' && /^[A-Z]{2}\d{2}[A-Z0-9]{1,30}$/.test(value)
/^[A-Z]{2}\d{2}[A-Z0-9]{1,30}$/.test(value)
? null ? null
: $t("validation.iban"); : $t('validation.iban');
case "bic": case 'bic':
return typeof value === "string" && return typeof value === 'string' &&
/^[A-Z]{6}[A-Z2-9][A-NP-Z0-9]([A-Z0-9]{3})?$/.test(value) /^[A-Z]{6}[A-Z2-9][A-NP-Z0-9]([A-Z0-9]{3})?$/.test(value)
? null ? null
: $t("validation.bic"); : $t('validation.bic');
case "licence_number": case 'licence_number':
return typeof value === "string" && value.length == 11 return typeof value === 'string' && value.length == 11 ? null : $t('validation.licence');
? null
: $t("validation.licence");
default: default:
return typeof value === "string" && !value.trim() && required return typeof value === 'string' && !value.trim() && required
? $t("validation.required") ? $t('validation.required')
: null; : null;
} }
} }
$: error = validateField(name, value, required); $: error = validateField(name, value, required);
$: selectedOption = options.find((option) => option.value == value); $: selectedOption = options.find((option) => option.value == value);
$: selectedColor = selectedOption ? selectedOption.color : ""; $: selectedColor = selectedOption ? selectedOption.color : '';
</script> </script>
<div class="input-box {type === 'checkbox' ? 'checkbox-container' : ''}"> <div class="input-box {type === 'checkbox' ? 'checkbox-container' : ''}">
{#if type === "checkbox"} {#if type === 'checkbox'}
<label class="form-control {readonly ? 'form-control--disabled' : ''}"> <label class="form-control {readonly ? 'form-control--disabled' : ''}">
<input <input
type="checkbox" type="checkbox"
@@ -140,19 +131,19 @@
{#if error} {#if error}
<span class="error-message">{error}</span> <span class="error-message">{error}</span>
{/if} {/if}
{#if type === "select"} {#if type === 'select'}
<select <select
{name} {name}
bind:value bind:value
{required} {required}
class="input select" class="input select"
style={selectedColor ? `color: ${selectedColor};` : ""} style={selectedColor ? `color: ${selectedColor};` : ''}
> >
{#each options as option} {#each options as option}
<option value={option.value}>{option.label}</option> <option value={option.value}>{option.label}</option>
{/each} {/each}
</select> </select>
{:else if type === "textarea"} {:else if type === 'textarea'}
<textarea <textarea
{name} {name}
{placeholder} {placeholder}
@@ -162,8 +153,8 @@
{rows} {rows}
class="input textarea {readonly ? 'readonly' : ''}" class="input textarea {readonly ? 'readonly' : ''}"
style="height:{rows * 1.5}em;" style="height:{rows * 1.5}em;"
/> ></textarea>
{:else if type != "checkbox"} {:else if type != 'checkbox'}
<input <input
{name} {name}
{type} {type}
@@ -181,8 +172,8 @@
<style> <style>
:root { :root {
--form-control-color: #6bff55; --form-control-color: var(--green); /* Changed from #6bff55 */
--form-control-disabled: #959495; --form-control-disabled: var(--overlay0); /* Changed from #959495 */
} }
.form-control { .form-control {
@@ -194,6 +185,7 @@
gap: 0.75em; gap: 0.75em;
align-items: center; align-items: center;
opacity: 0.8; opacity: 0.8;
color: var(--text);
} }
.form-control--disabled { .form-control--disabled {
@@ -201,16 +193,16 @@
cursor: not-allowed; cursor: not-allowed;
} }
input[type="checkbox"] { input[type='checkbox'] {
-webkit-appearance: none; -webkit-appearance: none;
appearance: none; appearance: none;
background-color: var(--form-background); background-color: var(--surface0);
margin: 0; margin: 0;
font: inherit; font: inherit;
color: currentColor; color: var(--text);
width: 1.75em; width: 1.75em;
height: 1.75em; height: 1.75em;
border: 0.15em solid currentColor; border: 0.15em solid var(--overlay0);
border-radius: 0.5em; border-radius: 0.5em;
transform: translateY(-0.075em); transform: translateY(-0.075em);
display: grid; display: grid;
@@ -218,8 +210,8 @@
transition: transform 0.2s ease-in-out; transition: transform 0.2s ease-in-out;
} }
input[type="checkbox"]::before { input[type='checkbox']::before {
content: ""; content: '';
width: 1em; width: 1em;
height: 1em; height: 1em;
clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%); clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%);
@@ -227,39 +219,41 @@
transform-origin: bottom left; transform-origin: bottom left;
transition: 120ms transform ease-in-out; transition: 120ms transform ease-in-out;
box-shadow: inset 1em 1em var(--form-control-color); box-shadow: inset 1em 1em var(--form-control-color);
background-color: CanvasText; background-color: var(--crust);
} }
input[type="checkbox"]:checked::before { input[type='checkbox']:checked::before {
transform: scale(1); transform: scale(1);
} }
input[type="checkbox"]:hover { input[type='checkbox']:hover {
outline: max(2px, 0.15em) solid currentColor; outline: max(2px, 0.15em) solid var(--lavender);
outline-offset: max(2px, 0.15em); outline-offset: max(2px, 0.15em);
transform: scale(1.3); transform: scale(1.3);
} }
input[type="checkbox"]:disabled { input[type='checkbox']:disabled {
--form-control-color: var(--form-control-disabled); --form-control-color: var(--form-control-disabled);
color: var(--form-control-disabled); color: var(--form-control-disabled);
cursor: not-allowed; cursor: not-allowed;
} }
.readonly { .readonly {
background-color: #ececec; background-color: var(--surface0);
cursor: not-allowed; cursor: not-allowed;
opacity: 0.7; opacity: 0.7;
color: #4f4f4f; color: var(--overlay1);
} }
.checkbox-container { .checkbox-container {
display: inline-flex; display: inline-flex;
align-items: center; align-items: center;
background-color: transparent; background-color: transparent;
margin: 0.5rem 0;
} }
.checkbox-text { .checkbox-text {
font-size: 16px; font-size: 16px;
color: var(--text);
} }
@media (min-width: 768px) { @media (min-width: 768px) {
@@ -270,16 +264,18 @@
} }
.select { .select {
padding-right: 1.5em; padding-right: 1.5em;
background-color: var(--surface0);
} }
.input-error-container { .input-error-container {
display: flex; display: flex;
align-items: flex-end; flex-direction: column;
gap: 0.5rem;
width: 100%; width: 100%;
max-width: 444px; max-width: 444px;
} }
.error-message { .error-message {
color: #eb5424; color: var(--red); /* Changed from #eb5424 */
font-size: 12px; font-size: 12px;
margin-bottom: 5px; margin-bottom: 5px;
align-self: flex-start; align-self: flex-start;
@@ -287,18 +283,56 @@
.input { .input {
width: 100%; width: 100%;
margin: 0.5rem 0;
} }
input, input,
textarea, textarea,
select { select {
width: 100%; width: 100%;
padding: 0.5rem; padding: 0.75rem 0;
border: 1px solid #ccc; background-color: var(--surface0);
border-radius: 4px; border: 1px solid var(--overlay0);
color: white; border-radius: 6px;
color: var(--text);
transition: border-color 0.2s ease-in-out;
} }
input:focus,
textarea:focus,
select:focus {
outline: none;
border-color: var(--lavender);
}
input:hover:not(:disabled),
textarea:hover:not(:disabled),
select:hover:not(:disabled) {
border-color: var(--overlay2);
}
textarea { textarea {
resize: vertical; resize: vertical;
min-height: 100px; min-height: 100px;
} }
/* Add consistent spacing between input boxes */
.input-box {
margin: 1rem 0;
padding: 0.5rem;
background-color: var(--surface0);
border-radius: 6px;
}
.input-box .label {
display: block;
margin-bottom: 0.5rem;
color: var(--lavender);
font-weight: 500;
}
/* Style select dropdown */
select option {
background-color: var(--base);
color: var(--text);
padding: 0.5rem;
}
</style> </style>

View File

@@ -1,8 +1,8 @@
<script> <script>
import { quintOut } from "svelte/easing"; import { quintOut } from 'svelte/easing';
import { t } from "svelte-i18n"; import { t } from 'svelte-i18n';
import { createEventDispatcher } from "svelte"; 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;
@@ -16,31 +16,26 @@
scale(${t}) scale(${t})
translateY(${u * -100}%) translateY(${u * -100}%)
`; `;
}, }
}; };
}; };
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
function closeModal() { function closeModal() {
dispatch("close", {}); dispatch('close', {});
} }
</script> </script>
<div class="modal-background"> <div class="modal-background">
<div <div transition:modal|global={{ duration: 1000 }} class="modal" role="dialog" aria-modal="true">
transition:modal|global={{ duration: 1000 }}
class="modal"
role="dialog"
aria-modal="true"
>
<!-- svelte-ignore a11y-missing-attribute --> <!-- svelte-ignore a11y-missing-attribute -->
<a <a
title={$t("cancel")} title={$t('cancel')}
class="modal-close" class="modal-close"
on:click={closeModal} on:click={closeModal}
role="button" role="button"
tabindex="0" tabindex="0"
on:keydown={(e) => e.key == "Enter" && closeModal()} on:keydown={(e) => e.key == 'Enter' && closeModal()}
> >
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
@@ -53,7 +48,7 @@
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" 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> </svg>
<span class="sr-only">{$t("cancel")}</span> <span class="sr-only">{$t('cancel')}</span>
</a> </a>
<div class="container"> <div class="container">
<slot /> <slot />
@@ -69,7 +64,8 @@
top: 0; top: 0;
right: 0; right: 0;
bottom: 0; bottom: 0;
background: rgba(0, 0, 0, 0.9); background: rgba(30, 30, 46, 0.65); /* var(--base) with 0.75 opacity */
backdrop-filter: blur(4px); /* Optional: adds a slight blur effect */
z-index: 9999; z-index: 9999;
display: flex; display: flex;
} }
@@ -79,7 +75,10 @@
left: 50%; left: 50%;
top: 50%; top: 50%;
width: 70%; width: 70%;
box-shadow: 0 0 10px hsl(0 0% 0% / 10%); background-color: var(--base);
border: 1px solid var(--surface0);
border-radius: 8px;
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 { .sr-only {
@@ -100,24 +99,52 @@
} }
.modal-close { .modal-close {
border: none; border: none;
padding: 1rem;
cursor: pointer;
position: absolute;
right: 0;
top: 0;
} }
.modal-close svg { .modal-close svg {
display: block; display: block;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
fill: rgb(14 165 233 /1); transition: all 0.3s ease-in-out;
transition: all 0.5s; fill: var(--blue); /* Using Catppuccin blue */
} }
.modal-close:hover svg { .modal-close:hover svg {
fill: rgb(225 29 72); fill: var(--red); /* Using Catppuccin red */
transform: scale(1.5); transform: scale(1.2);
} }
.modal .container { .modal .container {
max-height: 90vh; max-height: 90vh;
overflow-y: auto; overflow-y: auto;
align-items: center; align-items: center;
padding: 2rem;
background-color: var(--base);
border-radius: 8px;
} }
/* Scrollbar styling */
.modal .container::-webkit-scrollbar {
width: 8px;
}
.modal .container::-webkit-scrollbar-track {
background: var(--surface0);
border-radius: 4px;
}
.modal .container::-webkit-scrollbar-thumb {
background: var(--surface2);
border-radius: 4px;
}
.modal .container::-webkit-scrollbar-thumb:hover {
background: var(--surface1);
}
@media (min-width: 680px) { @media (min-width: 680px) {
.modal .container { .modal .container {
flex-direction: column; flex-direction: column;

View File

@@ -6,7 +6,7 @@
</script> </script>
<div class="loading"> <div class="loading">
<p class="simple-loader" style={width ? `width: ${width}px` : ""} /> <p class="simple-loader" style={width ? `width: ${width}px` : ''}></p>
{#if message} {#if message}
<p>{message}</p> <p>{message}</p>
{/if} {/if}

View File

@@ -1,10 +1,10 @@
<script> <script>
import InputField from "$lib/components/InputField.svelte"; import InputField from '$lib/components/InputField.svelte';
import SmallLoader from "$lib/components/SmallLoader.svelte"; import SmallLoader from '$lib/components/SmallLoader.svelte';
import { createEventDispatcher } from "svelte"; import { createEventDispatcher } from 'svelte';
import { applyAction, enhance } from "$app/forms"; import { applyAction, enhance } from '$app/forms';
import { receive, send } from "$lib/utils/helpers"; import { receive, send } from '$lib/utils/helpers';
import { t } from "svelte-i18n"; import { t } from 'svelte-i18n';
/** @type {import('../../routes/auth/about/[id]/$types').ActionData} */ /** @type {import('../../routes/auth/about/[id]/$types').ActionData} */
export let form; export let form;
@@ -15,49 +15,49 @@
/** @type {App.Locals['user']} */ /** @type {App.Locals['user']} */
const blankUser = { const blankUser = {
id: 0, id: 0,
email: "", email: '',
first_name: "", first_name: '',
last_name: "", last_name: '',
phone: "", phone: '',
address: "", address: '',
zip_code: "", zip_code: '',
city: "", city: '',
company: "", company: '',
date_of_birth: "", date_of_birth: '',
notes: "", notes: '',
profile_picture: "", profile_picture: '',
payment_status: 0, payment_status: 0,
status: 1, status: 1,
role_id: 0, role_id: 0,
membership: { membership: {
id: 0, id: 0,
start_date: "", start_date: '',
end_date: "", end_date: '',
status: 3, status: 3,
parent_member_id: 0, parent_member_id: 0,
subscription_model: { subscription_model: {
id: 0, id: 0,
name: "", name: ''
}, }
}, },
licence: { licence: {
id: 0, id: 0,
status: 1, status: 1,
licence_number: "", licence_number: '',
issued_date: "", issued_date: '',
expiration_date: "", expiration_date: '',
country: "", country: '',
licence_categories: [], licence_categories: []
}, },
bank_account: { bank_account: {
id: 0, id: 0,
mandate_date_signed: "", mandate_date_signed: '',
bank: "", bank: '',
account_holder_name: "", account_holder_name: '',
iban: "", iban: '',
bic: "", bic: '',
mandate_reference: "", mandate_reference: ''
}, }
}; };
/** @type {App.Locals['user'] | null} */ /** @type {App.Locals['user'] | null} */
@@ -75,7 +75,7 @@
...user, ...user,
licence: user.licence || blankUser.licence, licence: user.licence || blankUser.licence,
membership: user.membership || blankUser.membership, membership: user.membership || blankUser.membership,
bank_account: user.bank_account || blankUser.bank_account, bank_account: user.bank_account || blankUser.bank_account
}; };
} }
} }
@@ -84,60 +84,58 @@
$: isLoading = user === undefined; $: isLoading = user === undefined;
$: { $: {
console.log("incomingUser:", user); console.log('incomingUser:', user);
} }
// Add debug logging for user // Add debug logging for user
$: { $: {
console.log("processed user:", user); console.log('processed user:', user);
} }
/** @type {App.Locals['licence_categories']} */ /** @type {App.Locals['licence_categories']} */
export let licence_categories; export let licence_categories;
const userStatusOptions = [ const userStatusOptions = [
{ value: 1, label: $t("userStatus.1"), color: "#b1b1b1" }, // Grey for "Nicht verifiziert" { value: 1, label: $t('userStatus.1'), color: '#b1b1b1' }, // Grey for "Nicht verifiziert"
{ value: 2, label: $t("userStatus.2"), color: "#90EE90" }, // Light green for "Verifiziert" { value: 2, label: $t('userStatus.2'), color: '#90EE90' }, // Light green for "Verifiziert"
{ value: 3, label: $t("userStatus.3"), color: "#00bc00" }, // Green for "Aktiv" { value: 3, label: $t('userStatus.3'), color: '#00bc00' }, // Green for "Aktiv"
{ value: 4, label: $t("userStatus.4"), color: "#FFC0CB" }, // Pink for "Passiv" { value: 4, label: $t('userStatus.4'), color: '#FFC0CB' }, // Pink for "Passiv"
{ value: 5, label: $t("userStatus.5"), color: "#FF4646" }, // Red for "Deaktiviert" { value: 5, label: $t('userStatus.5'), color: '#FF4646' } // Red for "Deaktiviert"
]; ];
const userRoleOptions = [ const userRoleOptions = [
{ value: 0, label: $t("userRole.0"), color: "#b1b1b1" }, // Grey for "Mitglied" { value: 0, label: $t('userRole.0'), color: '#b1b1b1' }, // Grey for "Mitglied"
{ value: 1, label: $t("userRole.1"), color: "#00bc00" }, // Green for "Betrachter" { value: 1, label: $t('userRole.1'), color: '#00bc00' }, // Green for "Betrachter"
{ value: 4, label: $t("userRole.4"), color: "#FFC0CB" }, // Pink for "Bearbeiter" { value: 4, label: $t('userRole.4'), color: '#FFC0CB' }, // Pink for "Bearbeiter"
{ value: 8, label: $t("userRole.8"), color: "#FF4646" }, // Red for "Admin" { value: 8, label: $t('userRole.8'), color: '#FF4646' } // Red for "Admin"
]; ];
const membershipStatusOptions = [ const membershipStatusOptions = [
{ value: 3, label: $t("userStatus.3"), color: "#00bc00" }, // Green for "Aktiv" { value: 3, label: $t('userStatus.3'), color: '#00bc00' }, // Green for "Aktiv"
{ value: 4, label: $t("userStatus.4"), color: "#FFC0CB" }, // Pink for "Passiv" { value: 4, label: $t('userStatus.4'), color: '#FFC0CB' }, // Pink for "Passiv"
{ value: 5, label: $t("userStatus.5"), color: "#FF4646" }, // Red for "Deaktiviert" { value: 5, label: $t('userStatus.5'), color: '#FF4646' } // Red for "Deaktiviert"
]; ];
const licenceStatusOptions = [ const licenceStatusOptions = [
{ value: 1, label: $t("userStatus.1"), color: "#b1b1b1" }, // Grey for "Nicht verifiziert" { value: 1, label: $t('userStatus.1'), color: '#b1b1b1' }, // Grey for "Nicht verifiziert"
{ value: 3, label: $t("userStatus.3"), color: "#00bc00" }, // Green for "Aktiv" { value: 3, label: $t('userStatus.3'), color: '#00bc00' }, // Green for "Aktiv"
{ value: 4, label: $t("userStatus.4"), color: "#FFC0CB" }, // Pink for "Passiv" { value: 4, label: $t('userStatus.4'), color: '#FFC0CB' }, // Pink for "Passiv"
{ value: 5, label: $t("userStatus.5"), color: "#FF4646" }, // Red for "Deaktiviert" { value: 5, label: $t('userStatus.5'), color: '#FF4646' } // Red for "Deaktiviert"
]; ];
const dispatch = createEventDispatcher(); const dispatch = createEventDispatcher();
const TABS = ["profile", "licence", "membership", "bankaccount"]; const TABS = ['profile', 'licence', 'membership', 'bankaccount'];
let activeTab = TABS[0]; let activeTab = TABS[0];
let isUpdating = false, let isUpdating = false,
password = "", password = '',
password2 = ""; password2 = '';
/** @type {Object.<string, App.Locals['licence_categories']>} */ /** @type {Object.<string, App.Locals['licence_categories']>} */
$: groupedCategories = groupCategories(licence_categories); $: groupedCategories = groupCategories(licence_categories);
$: subscriptionModelOptions = subscriptions.map((sub) => ({ $: subscriptionModelOptions = subscriptions.map((sub) => ({
value: sub?.name ?? "", value: sub?.name ?? '',
label: sub?.name ?? "", label: sub?.name ?? ''
})); }));
$: selectedSubscriptionModel = $: selectedSubscriptionModel =
subscriptions.find( subscriptions.find((sub) => sub?.id === localUser.membership?.subscription_model.id) || null;
(sub) => sub?.id === localUser.membership?.subscription_model.id
) || null;
/** /**
* creates groups of categories depending on the first letter * creates groups of categories depending on the first letter
@@ -148,10 +146,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] = [];
@@ -176,12 +171,10 @@
isUpdating = true; isUpdating = true;
return async ({ result }) => { return async ({ result }) => {
isUpdating = false; isUpdating = false;
if (result.type === "success" || result.type === "redirect") { if (result.type === 'success' || result.type === 'redirect') {
close(); close();
} else { } else {
document document.querySelector('.modal .container')?.scrollTo({ top: 0, behavior: 'smooth' });
.querySelector(".modal .container")
?.scrollTo({ top: 0, behavior: "smooth" });
} }
await applyAction(result); await applyAction(result);
}; };
@@ -189,24 +182,19 @@
</script> </script>
{#if isLoading} {#if isLoading}
<SmallLoader width={30} message={$t("loading.user_data")} /> <SmallLoader width={30} message={$t('loading.user_data')} />
{:else if localUser} {:else if localUser}
<form <form class="content" action="?/updateUser" method="POST" use:enhance={handleUpdate}>
class="content"
action="?/updateUser"
method="POST"
use:enhance={handleUpdate}
>
<input name="user[id]" type="hidden" bind:value={localUser.id} /> <input name="user[id]" type="hidden" bind:value={localUser.id} />
<h1 class="step-title" style="text-align: center;">{$t("user.edit")}</h1> <h1 class="step-title" style="text-align: center;">{$t('user.edit')}</h1>
{#if form?.success} {#if form?.success}
<h4 <h4
class="step-subtitle warning" class="step-subtitle warning"
in:receive|global={{ key: Math.floor(Math.random() * 100) }} in:receive|global={{ key: Math.floor(Math.random() * 100) }}
out:send|global={{ key: Math.floor(Math.random() * 100) }} out:send|global={{ key: Math.floor(Math.random() * 100) }}
> >
Um einen fehlerhaften upload Ihres Bildes zu vermeiden, clicke bitte auf Um einen fehlerhaften upload Ihres Bildes zu vermeiden, clicke bitte auf den "Update" Button
den "Update" Button unten. unten.
</h4> </h4>
{/if} {/if}
{#if form?.errors} {#if form?.errors}
@@ -216,7 +204,7 @@
in:receive|global={{ key: error.id }} in:receive|global={{ key: error.id }}
out:send|global={{ key: error.id }} out:send|global={{ key: error.id }}
> >
{$t(error.field) + ": " + $t(error.key)} {$t(error.field) + ': ' + $t(error.key)}
</h4> </h4>
{/each} {/each}
{/if} {/if}
@@ -240,14 +228,11 @@
</button> </button>
{/each} {/each}
</div> </div>
<div <div class="tab-content" style="display: {activeTab === 'profile' ? 'block' : 'none'}">
class="tab-content"
style="display: {activeTab === 'profile' ? 'block' : 'none'}"
>
<InputField <InputField
name="user[status]" name="user[status]"
type="select" type="select"
label={$t("status")} label={$t('status')}
bind:value={localUser.status} bind:value={localUser.status}
options={userStatusOptions} options={userStatusOptions}
/> />
@@ -255,7 +240,7 @@
<InputField <InputField
name="user[role_id]" name="user[role_id]"
type="select" type="select"
label={$t("user.role")} label={$t('user.role')}
bind:value={localUser.role_id} bind:value={localUser.role_id}
options={userRoleOptions} options={userRoleOptions}
/> />
@@ -263,135 +248,132 @@
<InputField <InputField
name="user[password]" name="user[password]"
type="password" type="password"
label={$t("password")} label={$t('password')}
placeholder={$t("placeholder.password")} placeholder={$t('placeholder.password')}
bind:value={password} bind:value={password}
otherPasswordValue={password2} otherPasswordValue={password2}
/> />
<InputField <InputField
name="password2" name="password2"
type="password" type="password"
label={$t("password_repeat")} label={$t('password_repeat')}
placeholder={$t("placeholder.password")} placeholder={$t('placeholder.password')}
bind:value={password2} bind:value={password2}
otherPasswordValue={password} otherPasswordValue={password}
/> />
<InputField <InputField
name="user[first_name]" name="user[first_name]"
label={$t("first_name")} label={$t('first_name')}
bind:value={localUser.first_name} bind:value={localUser.first_name}
placeholder={$t("placeholder.first_name")} placeholder={$t('placeholder.first_name')}
required={true} required={true}
/> />
<InputField <InputField
name="user[last_name]" name="user[last_name]"
label={$t("last_name")} label={$t('last_name')}
bind:value={localUser.last_name} bind:value={localUser.last_name}
placeholder={$t("placeholder.last_name")} placeholder={$t('placeholder.last_name')}
required={true} required={true}
/> />
<InputField <InputField
name="user[company]" name="user[company]"
label={$t("company")} label={$t('company')}
bind:value={localUser.company} bind:value={localUser.company}
placeholder={$t("placeholder.company")} placeholder={$t('placeholder.company')}
/> />
<InputField <InputField
name="user[email]" name="user[email]"
type="email" type="email"
label={$t("email")} label={$t('email')}
bind:value={localUser.email} bind:value={localUser.email}
placeholder={$t("placeholder.email")} placeholder={$t('placeholder.email')}
required={true} required={true}
/> />
<InputField <InputField
name="user[phone]" name="user[phone]"
type="tel" type="tel"
label={$t("phone")} label={$t('phone')}
bind:value={localUser.phone} bind:value={localUser.phone}
placeholder={$t("placeholder.phone")} placeholder={$t('placeholder.phone')}
/> />
<InputField <InputField
name="user[date_of_birth]" name="user[date_of_birth]"
type="date" type="date"
label={$t("date_of_birth")} label={$t('date_of_birth')}
bind:value={localUser.date_of_birth} bind:value={localUser.date_of_birth}
placeholder={$t("placeholder.date_of_birth")} placeholder={$t('placeholder.date_of_birth')}
/> />
<InputField <InputField
name="user[address]" name="user[address]"
label={$t("address")} label={$t('address')}
bind:value={localUser.address} bind:value={localUser.address}
placeholder={$t("placeholder.address")} placeholder={$t('placeholder.address')}
/> />
<InputField <InputField
name="user[zip_code]" name="user[zip_code]"
label={$t("zip_code")} label={$t('zip_code')}
bind:value={localUser.zip_code} bind:value={localUser.zip_code}
placeholder={$t("placeholder.zip_code")} placeholder={$t('placeholder.zip_code')}
/> />
<InputField <InputField
name="user[city]" name="user[city]"
label={$t("city")} label={$t('city')}
bind:value={localUser.city} bind:value={localUser.city}
placeholder={$t("placeholder.city")} placeholder={$t('placeholder.city')}
/> />
<InputField <InputField
name="user[notes]" name="user[notes]"
type="textarea" type="textarea"
label={$t("notes")} label={$t('notes')}
bind:value={localUser.notes} bind:value={localUser.notes}
placeholder={$t("placeholder.notes", { placeholder={$t('placeholder.notes', {
values: { name: localUser.first_name || "" }, values: { name: localUser.first_name || '' }
})} })}
rows={10} rows={10}
/> />
</div> </div>
<div <div class="tab-content" style="display: {activeTab === 'licence' ? 'block' : 'none'}">
class="tab-content"
style="display: {activeTab === 'licence' ? 'block' : 'none'}"
>
<InputField <InputField
name="user[licence][status]" name="user[licence][status]"
type="select" type="select"
label={$t("status")} label={$t('status')}
bind:value={localUser.licence.status} bind:value={localUser.licence.status}
options={licenceStatusOptions} options={licenceStatusOptions}
/> />
<InputField <InputField
name="user[licence][number]" name="user[licence][number]"
type="text" type="text"
label={$t("licence_number")} label={$t('licence_number')}
bind:value={localUser.licence.licence_number} bind:value={localUser.licence.licence_number}
placeholder={$t("placeholder.licence_number")} placeholder={$t('placeholder.licence_number')}
toUpperCase={true} toUpperCase={true}
/> />
<InputField <InputField
name="user[licence][issued_date]" name="user[licence][issued_date]"
type="date" type="date"
label={$t("issued_date")} label={$t('issued_date')}
bind:value={localUser.licence.issued_date} bind:value={localUser.licence.issued_date}
placeholder={$t("placeholder.issued_date")} placeholder={$t('placeholder.issued_date')}
/> />
<InputField <InputField
name="user[licence][expiration_date]" name="user[licence][expiration_date]"
type="date" type="date"
label={$t("expiration_date")} label={$t('expiration_date')}
bind:value={localUser.licence.expiration_date} bind:value={localUser.licence.expiration_date}
placeholder={$t("placeholder.expiration_date")} placeholder={$t('placeholder.expiration_date')}
/> />
<InputField <InputField
name="user[licence][country]" name="user[licence][country]"
label={$t("country")} label={$t('country')}
bind:value={localUser.licence.country} bind:value={localUser.licence.country}
placeholder={$t("placeholder.issuing_country")} placeholder={$t('placeholder.issuing_country')}
/> />
<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 [group, categories], groupIndex}
{#if groupIndex > 0} {#if groupIndex > 0}
<div class="category-break" /> <div class="category-break"></div>
{/if} {/if}
{#each categories as category} {#each categories as category}
<div class="checkbox-item"> <div class="checkbox-item">
@@ -416,55 +398,52 @@
</div> </div>
</div> </div>
</div> </div>
<div <div class="tab-content" style="display: {activeTab === 'membership' ? 'block' : 'none'}">
class="tab-content"
style="display: {activeTab === 'membership' ? 'block' : 'none'}"
>
<InputField <InputField
name="user[membership][status]" name="user[membership][status]"
type="select" type="select"
label={$t("status")} label={$t('status')}
bind:value={localUser.membership.status} bind:value={localUser.membership.status}
options={membershipStatusOptions} options={membershipStatusOptions}
/> />
<InputField <InputField
name="user[membership][subscription_model][name]" name="user[membership][subscription_model][name]"
type="select" type="select"
label={$t("subscription_model")} label={$t('subscription_model')}
bind:value={localUser.membership.subscription_model.name} bind:value={localUser.membership.subscription_model.name}
options={subscriptionModelOptions} options={subscriptionModelOptions}
/> />
<div class="subscription-info"> <div class="subscription-info">
<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>
<strong>{$t("included_hours_per_year")}:</strong> <strong>{$t('included_hours_per_year')}:</strong>
{selectedSubscriptionModel?.included_hours_per_year} {selectedSubscriptionModel?.included_hours_per_year}
</p> </p>
{/if} {/if}
{#if selectedSubscriptionModel?.included_hours_per_month} {#if selectedSubscriptionModel?.included_hours_per_month}
<p> <p>
<strong>{$t("included_hours_per_month")}:</strong> <strong>{$t('included_hours_per_month')}:</strong>
{selectedSubscriptionModel?.included_hours_per_month} {selectedSubscriptionModel?.included_hours_per_month}
</p> </p>
{/if} {/if}
</div> </div>
<div class="subscription-column"> <div class="subscription-column">
<p> <p>
<strong>{$t("details")}:</strong> <strong>{$t('details')}:</strong>
{selectedSubscriptionModel?.details || "-"} {selectedSubscriptionModel?.details || '-'}
</p> </p>
{#if selectedSubscriptionModel?.conditions} {#if selectedSubscriptionModel?.conditions}
<p> <p>
<strong>{$t("conditions")}:</strong> <strong>{$t('conditions')}:</strong>
{selectedSubscriptionModel?.conditions} {selectedSubscriptionModel?.conditions}
</p> </p>
{/if} {/if}
@@ -473,64 +452,61 @@
<InputField <InputField
name="user[membership][start_date]" name="user[membership][start_date]"
type="date" type="date"
label={$t("start")} label={$t('start')}
bind:value={localUser.membership.start_date} bind:value={localUser.membership.start_date}
placeholder={$t("placeholder.start_date")} placeholder={$t('placeholder.start_date')}
/> />
<InputField <InputField
name="user[membership][end_date]" name="user[membership][end_date]"
type="date" type="date"
label={$t("end")} label={$t('end')}
bind:value={localUser.membership.end_date} bind:value={localUser.membership.end_date}
placeholder={$t("placeholder.end_date")} placeholder={$t('placeholder.end_date')}
/> />
<InputField <InputField
name="user[membership][parent_member_id]" name="user[membership][parent_member_id]"
type="number" type="number"
label={$t("parent_member_id")} label={$t('parent_member_id')}
bind:value={localUser.membership.parent_member_id} bind:value={localUser.membership.parent_member_id}
placeholder={$t("placeholder.parent_member_id")} placeholder={$t('placeholder.parent_member_id')}
/> />
</div> </div>
<div <div class="tab-content" style="display: {activeTab === 'bankaccount' ? 'block' : 'none'}">
class="tab-content"
style="display: {activeTab === 'bankaccount' ? 'block' : 'none'}"
>
<InputField <InputField
name="user[bank_account][account_holder_name]" name="user[bank_account][account_holder_name]"
label={$t("bank_account_holder")} label={$t('bank_account_holder')}
bind:value={localUser.bank_account.account_holder_name} bind:value={localUser.bank_account.account_holder_name}
placeholder={$t("placeholder.bank_account_holder")} placeholder={$t('placeholder.bank_account_holder')}
/> />
<InputField <InputField
name="user[bank_account][bank_name]" name="user[bank_account][bank_name]"
label={$t("bank_name")} label={$t('bank_name')}
bind:value={localUser.bank_account.bank} bind:value={localUser.bank_account.bank}
placeholder={$t("placeholder.bank_name")} placeholder={$t('placeholder.bank_name')}
/> />
<InputField <InputField
name="user[bank_account][iban]" name="user[bank_account][iban]"
label={$t("iban")} label={$t('iban')}
bind:value={localUser.bank_account.iban} bind:value={localUser.bank_account.iban}
placeholder={$t("placeholder.iban")} placeholder={$t('placeholder.iban')}
toUpperCase={true} toUpperCase={true}
/> />
<InputField <InputField
name="user[bank_account][bic]" name="user[bank_account][bic]"
label={$t("bic")} label={$t('bic')}
bind:value={localUser.bank_account.bic} bind:value={localUser.bank_account.bic}
placeholder={$t("placeholder.bic")} placeholder={$t('placeholder.bic')}
toUpperCase={true} toUpperCase={true}
/> />
<InputField <InputField
name="user[bank_account][mandate_reference]" name="user[bank_account][mandate_reference]"
label={$t("mandate_reference")} label={$t('mandate_reference')}
bind:value={localUser.bank_account.mandate_reference} bind:value={localUser.bank_account.mandate_reference}
placeholder={$t("placeholder.mandate_reference")} placeholder={$t('placeholder.mandate_reference')}
/> />
<InputField <InputField
name="user[bank_account][mandate_date_signed]" name="user[bank_account][mandate_date_signed]"
label={$t("mandate_date_signed")} label={$t('mandate_date_signed')}
type="date" type="date"
bind:value={localUser.bank_account.mandate_date_signed} bind:value={localUser.bank_account.mandate_date_signed}
readonly={true} readonly={true}
@@ -538,16 +514,12 @@
</div> </div>
<div class="button-container"> <div class="button-container">
{#if isUpdating} {#if isUpdating}
<SmallLoader width={30} message={"Aktualisiere..."} /> <SmallLoader width={30} message={'Aktualisiere...'} />
{:else} {:else}
<button <button type="button" class="button-dark" on:click={() => dispatch('cancel')}>
type="button" {$t('cancel')}</button
class="button-dark"
on:click={() => dispatch("cancel")}
> >
{$t("cancel")}</button <button type="submit" class="button-dark">{$t('confirm')}</button>
>
<button type="submit" class="button-dark">{$t("confirm")}</button>
{/if} {/if}
</div> </div>
</form> </form>
@@ -557,7 +529,7 @@
.category-break { .category-break {
grid-column: 1 / -1; grid-column: 1 / -1;
height: 1px; height: 1px;
background-color: #ccc; background-color: var(--surface0);
margin-top: 10px; margin-top: 10px;
margin-left: 20%; margin-left: 20%;
width: 60%; width: 60%;
@@ -589,7 +561,7 @@
.checkbox-description { .checkbox-description {
flex: 1; flex: 1;
font-size: 15px; font-size: 15px;
color: #9b9b9b; color: var(--subtext0);
margin-left: 10px; margin-left: 10px;
} }
@@ -618,11 +590,16 @@
gap: 1rem; gap: 1rem;
margin-top: 1rem; margin-top: 1rem;
font-size: 0.9rem; font-size: 0.9rem;
background-color: var(--surface0);
padding: 1rem;
border-radius: 8px;
border: 1px solid var(--surface1);
} }
.subscription-column { .subscription-column {
flex: 1; flex: 1;
min-width: 200px; min-width: 200px;
color: var(--text);
} }
.subscription-column p { .subscription-column p {
@@ -632,10 +609,14 @@
.subscription-column strong { .subscription-column strong {
display: inline-block; display: inline-block;
min-width: 100px; min-width: 100px;
color: var(--lavender);
} }
.tab-content { .tab-content {
padding: 1rem; padding: 1rem;
border-radius: 0 0 3px 3px; border-radius: 0 0 3px 3px;
background-color: var(--surface0);
border: 1px solid var(--surface1);
margin-top: 1rem;
} }
.button-container { .button-container {
display: flex; display: flex;
@@ -650,7 +631,23 @@
flex: 1 1 0; flex: 1 1 0;
min-width: 120px; min-width: 120px;
max-width: calc(50% - 5px); max-width: calc(50% - 5px);
background-color: var(--surface1);
color: var(--text);
border: 1px solid var(--overlay0);
transition: all 0.2s ease-in-out;
} }
.button-container button:hover {
background-color: var(--surface2);
border-color: var(--lavender);
}
.button-container button.active {
background-color: var(--mauve);
border-color: var(--mauve);
color: var(--base);
}
@media (max-width: 480px) { @media (max-width: 480px) {
.button-container button { .button-container button {
flex-basis: 100%; flex-basis: 100%;

View File

@@ -1,23 +1,52 @@
:root {
--rosewater: #f5e0dc;
--flamingo: #f2cdcd;
--pink: #f5c2e7;
--mauve: #cba6f7;
--red: #f38ba8;
--maroon: #eba0ac;
--peach: #fab387;
--yellow: #f9e2af;
--green: #a6e3a1;
--teal: #94e2d5;
--sky: #89dceb;
--sapphire: #74c7ec;
--blue: #89b4fa;
--lavender: #b4befe;
--text: #cdd6f4;
--subtext1: #bac2de;
--subtext0: #a6adc8;
--overlay2: #9399b2;
--overlay1: #7f849c;
--overlay0: #6c7086;
--surface2: #585b70;
--surface1: #45475a;
--surface0: #313244;
--base: #1e1e2e;
--mantle: #181825;
--crust: #11111b;
}
@font-face { @font-face {
font-family: "Roboto Mono"; font-family: 'Roboto Mono';
font-style: normal; font-style: normal;
font-weight: 300; font-weight: 300;
src: url(https://fonts.gstatic.com/s/robotomono/v22/L0xuDF4xlVMF-BfR8bXMIhJHg45mwgGEFl0_gPq_ROW9.ttf) src: url(https://fonts.gstatic.com/s/robotomono/v22/L0xuDF4xlVMF-BfR8bXMIhJHg45mwgGEFl0_gPq_ROW9.ttf)
format("truetype"); format('truetype');
} }
@font-face { @font-face {
font-family: "Roboto Mono"; font-family: 'Roboto Mono';
font-style: normal; font-style: normal;
font-weight: 400; font-weight: 400;
src: url(https://fonts.gstatic.com/s/robotomono/v22/L0xuDF4xlVMF-BfR8bXMIhJHg45mwgGEFl0_3vq_ROW9.ttf) src: url(https://fonts.gstatic.com/s/robotomono/v22/L0xuDF4xlVMF-BfR8bXMIhJHg45mwgGEFl0_3vq_ROW9.ttf)
format("truetype"); format('truetype');
} }
html { html {
padding: 0 30px; padding: 0 30px;
background-color: black; background-color: var(--base);
color: #9b9b9b; color: var(--text);
font-family: "Quicksand", sans-serif; font-family: 'Quicksand', sans-serif;
font-size: 16px; font-size: 16px;
font-weight: normal; font-weight: normal;
} }
@@ -28,13 +57,13 @@ body {
pre, pre,
code { code {
display: inline; display: inline;
font-family: "Roboto Mono", monospace; font-family: 'Roboto Mono', monospace;
font-size: 16px; font-size: 16px;
} }
input { input {
font-family: "Roboto Mono", monospace; font-family: 'Roboto Mono', monospace;
color: white; color: var(--text);
border-style: none; border-style: none;
height: 21px; height: 21px;
font-size: 16px; font-size: 16px;
@@ -53,12 +82,12 @@ h6 {
} }
h2 { h2 {
margin: 0 0 45px 0; margin: 0 0 45px 0;
color: #fff; color: var(--lavender);
font-size: 36px; font-size: 36px;
} }
h3 { h3 {
margin: 0 0 2rem 0; margin: 0 0 2rem 0;
color: #fff; color: var(--lavender);
font-size: 32px; font-size: 32px;
} }
p { p {
@@ -72,16 +101,16 @@ a {
transition: border 0.2s ease-in-out; transition: border 0.2s ease-in-out;
border-bottom: 1px solid transparent; border-bottom: 1px solid transparent;
text-decoration: none; text-decoration: none;
color: #00b7ef; color: var(--blue);
} }
a:hover { a:hover {
border-bottom-color: #00b7ef; border-bottom-color: var(--blue);
} }
li { li {
line-height: 1.8; line-height: 1.8;
} }
li strong { li strong {
color: #fff; color: var(--text);
} }
.image { .image {
width: 100%; width: 100%;
@@ -117,7 +146,7 @@ li strong {
box-sizing: border-box; box-sizing: border-box;
width: 100%; width: 100%;
padding: 3em 0 0; padding: 3em 0 0;
background: black; background: var(--base);
} }
.header.top-banner-open { .header.top-banner-open {
margin-top: 5px; margin-top: 5px;
@@ -131,6 +160,7 @@ li strong {
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
margin: 0 auto; margin: 0 auto;
background-color: var(--base);
} }
.header .header-container .header-left { .header .header-container .header-left {
display: flex; display: flex;
@@ -142,7 +172,7 @@ li strong {
} }
.header .header-container .header-left .header-crafted-by-container a { .header .header-container .header-left .header-crafted-by-container a {
display: flex; display: flex;
color: #9b9b9b; color: var(--subtext0);
border: none; border: none;
} }
.header .header-container .header-left .header-crafted-by-container a img { .header .header-container .header-left .header-crafted-by-container a img {
@@ -154,7 +184,7 @@ li strong {
} }
.header .header-container .header-left .header-crafted-by-container .auth0 { .header .header-container .header-left .header-crafted-by-container .auth0 {
margin-left: 1ch; margin-left: 1ch;
color: #fff; color: var(--text);
font-weight: bold; font-weight: bold;
} }
.header .header-container .header-right { .header .header-container .header-right {
@@ -174,7 +204,7 @@ li strong {
} }
.header .header-container .header-right .header-nav-item.active a, .header .header-container .header-right .header-nav-item.active a,
.header .header-container .header-right .header-nav-item.active button { .header .header-container .header-right .header-nav-item.active button {
color: #fff; color: var(--text);
} }
.header .header-container .header-right a img { .header .header-container .header-right a img {
@@ -188,11 +218,12 @@ li strong {
display: block; display: block;
padding: 20px 0; padding: 20px 0;
border: none; border: none;
color: #9b9b9b; color: var(--subtext0);
} }
.header .header-container .header-right .header-nav-item:hover a, .header .header-container .header-right .header-nav-item:hover a,
.header .header-container .header-right .header-nav-item:hover button { .header .header-container .header-right .header-nav-item:hover button {
color: #fdfff5; color: var(--lavender);
} }
@media (min-width: 680px) { @media (min-width: 680px) {
.header { .header {
@@ -212,7 +243,9 @@ li strong {
} }
} }
.button-dark { .button-dark {
transition: border-color 0.3s ease-in-out, background-color 0.3s ease-in-out; transition:
border-color 0.3s ease-in-out,
background-color 0.3s ease-in-out;
color: white; color: white;
text-transform: uppercase; text-transform: uppercase;
font-weight: 500; font-weight: 500;
@@ -220,52 +253,58 @@ li strong {
letter-spacing: 1px; letter-spacing: 1px;
cursor: pointer; cursor: pointer;
background-color: transparent; background-color: transparent;
border: 1px solid #595b5c; border: 1px solid var(--surface1);
margin: 2px; margin: 2px;
} }
.button-dark:hover { .button-dark:hover {
border-color: #fff; border-color: var(--text);
} }
.button-colorful { .button-colorful {
transition: border-color 0.3s ease-in-out, background-color 0.3s ease-in-out; transition:
border-color 0.3s ease-in-out,
background-color 0.3s ease-in-out;
color: white; color: white;
text-transform: uppercase; text-transform: uppercase;
font-weight: 500; font-weight: 500;
padding: 18px 28px; padding: 18px 28px;
letter-spacing: 1px; letter-spacing: 1px;
cursor: pointer; cursor: pointer;
background-color: #d43aff; background-color: var(--mauve);
border: 1px solid #d43aff; border: 1px solid var(--mauve);
} }
.button-colorful:hover { .button-colorful:hover {
background-color: #c907ff; background-color: #c907ff;
border-color: #c907ff; border-color: #c907ff;
} }
.button-orange { .button-orange {
transition: border-color 0.3s ease-in-out, background-color 0.3s ease-in-out; transition:
border-color 0.3s ease-in-out,
background-color 0.3s ease-in-out;
color: white; color: white;
text-transform: uppercase; text-transform: uppercase;
font-weight: 500; font-weight: 500;
padding: 18px 28px; padding: 18px 28px;
letter-spacing: 1px; letter-spacing: 1px;
cursor: pointer; cursor: pointer;
background-color: #eb5424; background-color: var(--peach);
border: 1px solid #eb5424; border: 1px solid var(--peach);
} }
.button-orange:hover { .button-orange:hover {
background-color: #ca3f12; background-color: #ca3f12;
border-color: #ca3f12; border-color: #ca3f12;
} }
.button-colorful:disabled { .button-colorful:disabled {
transition: border-color 0.3s ease-in-out, background-color 0.3s ease-in-out; transition:
border-color 0.3s ease-in-out,
background-color 0.3s ease-in-out;
color: white; color: white;
text-transform: uppercase; text-transform: uppercase;
font-weight: 500; font-weight: 500;
padding: 18px 28px; padding: 18px 28px;
letter-spacing: 1px; letter-spacing: 1px;
cursor: pointer; cursor: pointer;
background-color: #9a9a9a; background-color: var(--overlay0);
border: 1px solid #9a9a9a; border: 1px solid var(--overlay0);
} }
.hero-container { .hero-container {
max-width: 795px; max-width: 795px;
@@ -351,9 +390,9 @@ li strong {
width: 100%; width: 100%;
height: auto; height: auto;
box-sizing: border-box; box-sizing: border-box;
background-color: #2f2f2f; background-color: var(--surface0);
border-radius: 3px; border-radius: 3px;
font-family: "Roboto Mono", monospace; font-family: 'Roboto Mono', monospace;
font-size: 13px; font-size: 13px;
} }
.input-box .label { .input-box .label {
@@ -361,10 +400,10 @@ li strong {
font-size: 16px; font-size: 16px;
} }
.input-box .input { .input-box .input {
background-color: #494848; background-color: var(--surface1);
border: 3px solid var(--surface1);
border-radius: 6px; border-radius: 6px;
outline: none; outline: none;
border: 3px solid #494848;
width: 100%; width: 100%;
max-width: 444px; max-width: 444px;
font-size: 13px; font-size: 13px;
@@ -394,7 +433,7 @@ li strong {
} }
.btn { .btn {
font-family: "Roboto Mono", monospace; font-family: 'Roboto Mono', monospace;
letter-spacing: 1px; letter-spacing: 1px;
padding: 18px 28px; padding: 18px 28px;
border: 1px solid; border: 1px solid;
@@ -402,44 +441,46 @@ li strong {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 0.5rem; gap: 0.5rem;
transition: border-color 0.3s ease-in-out, background-color 0.3s ease-in-out; transition:
border-color 0.3s ease-in-out,
background-color 0.3s ease-in-out;
border-radius: 5px; border-radius: 5px;
} }
.btn.primary { .btn.primary {
background-color: #4361ee; background-color: var(--blue);
border-color: #4361ee; border-color: var(--blue);
color: white; color: white;
} }
.btn.primary:hover { .btn.primary:hover {
background-color: #3651d4; background-color: var(--sapphire);
border-color: #3651d4; border-color: var(--sapphire);
} }
.btn.danger { .btn.danger {
background-color: #dc2626; background-color: var(--red);
border-color: #dc2626; border-color: var(--red);
color: white; color: white;
} }
.btn.danger:hover { .btn.danger:hover {
background-color: #c51f1f; background-color: var(--maroon);
border-color: #c51f1f; border-color: var(--maroon);
} }
.warning { .warning {
margin: 20px 0; margin: 20px 0;
padding: 1rem; padding: 1rem;
width: 100%; width: 100%;
box-sizing: border-box; box-sizing: border-box;
background-color: rgb(255 228 230); background-color: var(--surface0);
border: 1px solid rgb(225 29 72); border: 1px solid var(--red);
color: var(--red);
border-radius: 6px; border-radius: 6px;
color: rgb(225 29 72);
font-size: 16px; font-size: 16px;
} }
.warning a { .warning a {
color: rgb(225 29 72); color: var(--red);
text-decoration: underline; text-decoration: underline;
} }
.warning.hidden { .warning.hidden {
@@ -449,8 +490,8 @@ li strong {
.error { .error {
margin-top: 10rem; margin-top: 10rem;
padding: 30px 40px; padding: 30px 40px;
background: #2f3132; background: var(--surface0);
color: #fff; color: var(--text);
} }
.error p { .error p {
margin: 0 0 1rem; margin: 0 0 1rem;
@@ -523,7 +564,7 @@ li strong {
--b: 20px; /* border thickness */ --b: 20px; /* border thickness */
--n: 15; /* number of dashes*/ --n: 15; /* number of dashes*/
--g: 7deg; /* gap between dashes*/ --g: 7deg; /* gap between dashes*/
--c: #d43aff; /* the color */ --c: var(--mauve); /* Changed loader color to match theme */
width: 40px; /* size */ width: 40px; /* size */
aspect-ratio: 1; aspect-ratio: 1;
@@ -535,11 +576,7 @@ li strong {
#000 1deg calc(360deg / var(--n) - var(--g) - 1deg), #000 1deg calc(360deg / var(--n) - var(--g) - 1deg),
#0000 calc(360deg / var(--n) - var(--g)) calc(360deg / var(--n)) #0000 calc(360deg / var(--n) - var(--g)) calc(360deg / var(--n))
), ),
radial-gradient( radial-gradient(farthest-side, #0000 calc(98% - var(--b)), #000 calc(100% - var(--b)));
farthest-side,
#0000 calc(98% - var(--b)),
#000 calc(100% - var(--b))
);
-webkit-mask: var(--_m); -webkit-mask: var(--_m);
mask: var(--_m); mask: var(--_m);
-webkit-mask-composite: destination-in; -webkit-mask-composite: destination-in;

View File

@@ -1,9 +1,9 @@
<script> <script>
import Modal from "$lib/components/Modal.svelte"; import Modal from '$lib/components/Modal.svelte';
import UserEditForm from "$lib/components/UserEditForm.svelte"; import UserEditForm from '$lib/components/UserEditForm.svelte';
import { onMount } from "svelte"; import { onMount } from 'svelte';
import { page } from "$app/stores"; import { page } from '$app/stores';
import { t } from "svelte-i18n"; import { t } from 'svelte-i18n';
/** @type {import('./$types').ActionData} */ /** @type {import('./$types').ActionData} */
export let form; export let form;
@@ -33,12 +33,10 @@
<span class="value block-value"> <span class="value block-value">
<span <span
>{$t(`userStatus.${user.status}`, { >{$t(`userStatus.${user.status}`, {
default: "unknown status", default: 'unknown status'
})}</span })}</span
> >
<span <span>{$t(`userRole.${user.role_id}`, { default: 'unknown role' })}</span>
>{$t(`userRole.${user.role_id}`, { default: "unknown role" })}</span
>
</span> </span>
</h3> </h3>
{/if} {/if}
@@ -75,7 +73,7 @@
{/if} {/if}
{#if user.notes} {#if user.notes}
<h3 class="hero-subtitle subtitle info-row"> <h3 class="hero-subtitle subtitle info-row">
<span class="label">{$t("notes")}:</span> <span class="label">{$t('notes')}:</span>
<span class="value">{user.notes}</span> <span class="value">{user.notes}</span>
</h3> </h3>
{/if} {/if}
@@ -88,13 +86,7 @@
{#if showModal} {#if showModal}
<Modal on:close={close}> <Modal on:close={close}>
<UserEditForm <UserEditForm {form} {user} {subscriptions} {licence_categories} on:cancel={close} />
{form}
{user}
{subscriptions}
{licence_categories}
on:cancel={close}
/>
</Modal> </Modal>
{/if} {/if}
@@ -116,6 +108,11 @@
align-items: start; align-items: start;
text-align: left; text-align: left;
margin-top: 1rem; margin-top: 1rem;
color: var(--text);
background-color: var(--surface0);
padding: 1.5rem;
border-radius: 8px;
border: 1px solid var(--surface1);
} }
.info-row { .info-row {
@@ -127,18 +124,21 @@
font-weight: bold; font-weight: bold;
text-align: left; text-align: left;
padding-right: 1rem; padding-right: 1rem;
color: var(--lavender);
} }
.value { .value {
margin: 0; margin: 0;
font-size: 1.2rem; font-size: 1.2rem;
text-align: left; text-align: left;
color: var(--text);
} }
.block-value { .block-value {
display: flex; display: flex;
align-items: flex-start; align-items: flex-start;
flex-direction: column; flex-direction: column;
color: var(--subtext0);
} }
.hero-buttons-container { .hero-buttons-container {

View File

@@ -1,8 +1,8 @@
<script> <script>
import Modal from "$lib/components/Modal.svelte"; import Modal from '$lib/components/Modal.svelte';
import UserEditForm from "$lib/components/UserEditForm.svelte"; import UserEditForm from '$lib/components/UserEditForm.svelte';
import { t } from "svelte-i18n"; import { t } from 'svelte-i18n';
import { page } from "$app/stores"; import { page } from '$app/stores';
/** @type {import('./$types').ActionData} */ /** @type {import('./$types').ActionData} */
export let form; export let form;
@@ -12,10 +12,10 @@
users = [], users = [],
licence_categories = [], licence_categories = [],
subscriptions = [], subscriptions = [],
payments = [], payments = []
} = $page.data); } = $page.data);
let activeSection = "users"; let activeSection = 'users';
/** @type{App.Locals['user'] | null} */ /** @type{App.Locals['user'] | null} */
let selectedUser = null; let selectedUser = null;
let showModal = false; let showModal = false;
@@ -57,7 +57,7 @@
class="nav-link {activeSection === 'users' ? 'active' : ''}" class="nav-link {activeSection === 'users' ? 'active' : ''}"
on:click={() => setActiveSection('users')} on:click={() => setActiveSection('users')}
> >
<i class="fas fa-users" /> <i class="fas fa-users"></i>
<span class="nav-badge">{users.length}</span> <span class="nav-badge">{users.length}</span>
{$t('users')} {$t('users')}
</button> </button>
@@ -67,7 +67,7 @@
class="nav-link {activeSection === 'subscriptions' ? 'active' : ''}" class="nav-link {activeSection === 'subscriptions' ? 'active' : ''}"
on:click={() => setActiveSection('subscriptions')} on:click={() => setActiveSection('subscriptions')}
> >
<i class="fas fa-clipboard-list" /> <i class="fas fa-clipboard-list"></i>
<span class="nav-badge">{subscriptions.length}</span> <span class="nav-badge">{subscriptions.length}</span>
{$t('subscriptions')} {$t('subscriptions')}
</button> </button>
@@ -77,7 +77,7 @@
class="nav-link {activeSection === 'payments' ? 'active' : ''}" class="nav-link {activeSection === 'payments' ? 'active' : ''}"
on:click={() => setActiveSection('payments')} on:click={() => setActiveSection('payments')}
> >
<i class="fas fa-credit-card" /> <i class="fas fa-credit-card"></i>
{$t('payments')} {$t('payments')}
</button> </button>
</li> </li>
@@ -90,7 +90,7 @@
<div class="section-header"> <div class="section-header">
<h2>{$t('users')}</h2> <h2>{$t('users')}</h2>
<button class="btn primary" on:click={() => openEditModal(null)}> <button class="btn primary" on:click={() => openEditModal(null)}>
<i class="fas fa-plus" /> <i class="fas fa-plus"></i>
{$t('add_new')} {$t('add_new')}
</button> </button>
</div> </div>
@@ -124,11 +124,11 @@
</table> </table>
<div class="button-group"> <div class="button-group">
<button class="btn primary" on:click={() => openEditModal(user)}> <button class="btn primary" on:click={() => openEditModal(user)}>
<i class="fas fa-edit" /> <i class="fas fa-edit"></i>
{$t('edit')} {$t('edit')}
</button> </button>
<button class="btn danger"> <button class="btn danger">
<i class="fas fa-trash" /> <i class="fas fa-trash"></i>
{$t('delete')} {$t('delete')}
</button> </button>
</div> </div>
@@ -140,7 +140,7 @@
<div class="section-header"> <div class="section-header">
<h2>{$t('subscriptions')}</h2> <h2>{$t('subscriptions')}</h2>
<button class="btn primary" on:click={() => openEditModal(null)}> <button class="btn primary" on:click={() => openEditModal(null)}>
<i class="fas fa-plus" /> <i class="fas fa-plus"></i>
{$t('add_new')} {$t('add_new')}
</button> </button>
</div> </div>