implemented permission system
This commit is contained in:
@@ -36,7 +36,7 @@
|
||||
default: 'unknown status'
|
||||
})}</span
|
||||
>
|
||||
<span>{$t(`userRole.${user.role_id}`, { default: 'unknown role' })}</span>
|
||||
<span>{$t(`userRole.${user.role_id}`, { default: 'unknown' })}</span>
|
||||
</span>
|
||||
</h3>
|
||||
{/if}
|
||||
@@ -93,7 +93,7 @@
|
||||
{licence_categories}
|
||||
on:close={close}
|
||||
on:cancel={close}
|
||||
role_id={user.role_id}
|
||||
editor={user}
|
||||
/>
|
||||
</Modal>
|
||||
{/if}
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// - Implement a load function to fetch a list of all users.
|
||||
// - Create actions for updating user information (similar to the about/[id] route).
|
||||
|
||||
import { BASE_API_URI } from '$lib/utils/constants';
|
||||
import { formatError, userDatesFromRFC3339 } from '$lib/utils/helpers';
|
||||
import { BASE_API_URI, PERMISSIONS } from '$lib/utils/constants';
|
||||
import { formatError, hasPrivilige, userDatesFromRFC3339 } from '$lib/utils/helpers';
|
||||
import { fail, redirect } from '@sveltejs/kit';
|
||||
import {
|
||||
formDataToObject,
|
||||
@@ -18,7 +18,7 @@ export async function load({ locals }) {
|
||||
if (!locals.user) {
|
||||
throw redirect(302, `${base}/auth/login?next=${base}/auth/admin/users`);
|
||||
}
|
||||
if (locals.user.role_id === 0) {
|
||||
if (!hasPrivilige(locals.user, PERMISSIONS.View)) {
|
||||
throw redirect(302, `${base}/auth/about/${locals.user.id}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,8 @@
|
||||
import { t } from 'svelte-i18n';
|
||||
import { page } from '$app/stores';
|
||||
import { applyAction, enhance } from '$app/forms';
|
||||
import { receive, send } from '$lib/utils/helpers';
|
||||
import { hasPrivilige, receive, send } from '$lib/utils/helpers';
|
||||
import { PERMISSIONS } from '$lib/utils/constants';
|
||||
|
||||
/** @type {import('./$types').ActionData} */
|
||||
export let form;
|
||||
@@ -19,7 +20,7 @@
|
||||
payments = []
|
||||
} = $page.data);
|
||||
|
||||
let activeSection = 'users';
|
||||
let activeSection = 'members';
|
||||
/** @type{App.Locals['user'] | null} */
|
||||
let selectedUser = null;
|
||||
/** @type{App.Types['subscription'] | null} */
|
||||
@@ -28,9 +29,21 @@
|
||||
let showUserModal = false;
|
||||
let searchTerm = '';
|
||||
|
||||
$: filteredUsers = searchTerm ? getFilteredUsers() : users;
|
||||
$: members = users.filter((/** @type{App.Locals['user']} */ user) => {
|
||||
return user.role_id >= PERMISSIONS.Member;
|
||||
});
|
||||
$: supporters = users.filter((/** @type{App.Locals['user']} */ user) => {
|
||||
return user.role_id < PERMISSIONS.Member;
|
||||
});
|
||||
$: filteredMembers = searchTerm ? getFilteredUsers(members) : members;
|
||||
|
||||
function handleMailButtonClick() {
|
||||
$: filteredSupporters = searchTerm ? getFilteredUsers(supporters) : supporters;
|
||||
|
||||
/**
|
||||
* Handles Mail button click to open a formatted mailto link
|
||||
* @param {App.Locals['user'][]} filteredUsers - the users to send the mail to
|
||||
*/
|
||||
function handleMailButtonClick(filteredUsers) {
|
||||
const subject = 'Important Announcement';
|
||||
const body = `Hello everyone,\n\nThis is an important message.`;
|
||||
const bccEmails = filteredUsers
|
||||
@@ -43,14 +56,15 @@
|
||||
}
|
||||
|
||||
/**
|
||||
* returns a set of users depending on the entered search query
|
||||
* returns a set of members depending on the entered search query
|
||||
* @param {App.Locals['user'][]} userSet Set to filter
|
||||
* @return {App.Locals['user'][]}*/
|
||||
const getFilteredUsers = () => {
|
||||
if (!searchTerm.trim()) return users;
|
||||
const getFilteredUsers = (userSet) => {
|
||||
if (!searchTerm.trim()) return userSet;
|
||||
|
||||
const term = searchTerm.trim().toLowerCase();
|
||||
|
||||
return users.filter((/** @type{App.Locals['user']}*/ user) => {
|
||||
return userSet.filter((/** @type{App.Locals['user']}*/ user) => {
|
||||
const basicMatch = [
|
||||
user.first_name?.toLowerCase(),
|
||||
user.last_name?.toLowerCase(),
|
||||
@@ -124,12 +138,22 @@
|
||||
<ul class="nav-list">
|
||||
<li>
|
||||
<button
|
||||
class="nav-link {activeSection === 'users' ? 'active' : ''}"
|
||||
on:click={() => setActiveSection('users')}
|
||||
class="nav-link {activeSection === 'members' ? 'active' : ''}"
|
||||
on:click={() => setActiveSection('members')}
|
||||
>
|
||||
<i class="fas fa-users"></i>
|
||||
{$t('users')}
|
||||
<span class="nav-badge">{users.length}</span>
|
||||
<span class="nav-badge">{members.length}</span>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button
|
||||
class="nav-link {activeSection === 'supporter' ? 'active' : ''}"
|
||||
on:click={() => setActiveSection('supporter')}
|
||||
>
|
||||
<i class="fas fa-hand-holding-dollar"></i>
|
||||
{$t('supporter')}
|
||||
<span class="nav-badge">{supporters.length}</span>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
@@ -168,7 +192,7 @@
|
||||
{/each}
|
||||
{/if}
|
||||
|
||||
{#if activeSection === 'users'}
|
||||
{#if activeSection === 'members'}
|
||||
<div class="section-header">
|
||||
<h2>{$t('users')}</h2>
|
||||
<div class="title-container">
|
||||
@@ -183,7 +207,7 @@
|
||||
<button
|
||||
class="btn primary"
|
||||
aria-label="Mail Users"
|
||||
on:click={() => handleMailButtonClick()}
|
||||
on:click={() => handleMailButtonClick(filteredMembers)}
|
||||
>
|
||||
<i class="fas fa-envelope"></i>
|
||||
</button>
|
||||
@@ -196,7 +220,108 @@
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion">
|
||||
{#each filteredUsers as user}
|
||||
{#each filteredMembers as user}
|
||||
<details class="accordion-item">
|
||||
<summary class="accordion-header">
|
||||
{user.first_name}
|
||||
{user.last_name}
|
||||
</summary>
|
||||
<div class="accordion-content">
|
||||
<table class="table">
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>{$t('user.id')}</th>
|
||||
<td>{user.id}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{$t('name')}</th>
|
||||
<td>{user.first_name} {user.last_name}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{$t('user.email')}</th>
|
||||
<td>{user.email}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{$t('subscription.subscription')}</th>
|
||||
<td>{user.membership?.subscription_model?.name}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>{$t('status')}</th>
|
||||
<td>{$t('userStatus.' + user.status)}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="button-group">
|
||||
<button class="btn primary" on:click={() => openEditUserModal(user)}>
|
||||
<i class="fas fa-edit"></i>
|
||||
{$t('edit')}
|
||||
</button>
|
||||
<form
|
||||
method="POST"
|
||||
action="?/userDelete"
|
||||
use:enhance={() => {
|
||||
return async ({ result }) => {
|
||||
if (result.type === 'success' || result.type === 'redirect') {
|
||||
await applyAction(result);
|
||||
}
|
||||
};
|
||||
}}
|
||||
on:submit|preventDefault={(/** @type {SubmitEvent} */ e) => {
|
||||
if (
|
||||
!confirm(
|
||||
$t('dialog.user_deletion', {
|
||||
values: {
|
||||
firstname: user.first_name || '',
|
||||
lastname: user.last_name || ''
|
||||
}
|
||||
})
|
||||
)
|
||||
) {
|
||||
e.preventDefault(); // Cancel form submission if user declines
|
||||
}
|
||||
}}
|
||||
>
|
||||
<input type="hidden" name="user[id]" value={user.id} />
|
||||
<input type="hidden" name="user[last_name]" value={user.last_name} />
|
||||
<button class="btn danger" type="submit">
|
||||
<i class="fas fa-trash"></i>
|
||||
{$t('delete')}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
{/each}
|
||||
</div>
|
||||
{:else if activeSection === 'supporter'}
|
||||
<div class="section-header">
|
||||
<h2>{$t('supporter')}</h2>
|
||||
<div class="title-container">
|
||||
<InputField
|
||||
name="search"
|
||||
bind:value={searchTerm}
|
||||
placeholder={$t('placeholder.search')}
|
||||
backgroundColor="--base"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<button
|
||||
class="btn primary"
|
||||
aria-label="Mail Supporter"
|
||||
on:click={() => handleMailButtonClick(filteredSupporters)}
|
||||
>
|
||||
<i class="fas fa-envelope"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn primary" on:click={() => openEditUserModal(null)}>
|
||||
<i class="fas fa-plus"></i>
|
||||
{$t('add_new')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="accordion">
|
||||
{#each filteredSupporters as user}
|
||||
<details class="accordion-item">
|
||||
<summary class="accordion-header">
|
||||
{user.first_name}
|
||||
@@ -272,7 +397,7 @@
|
||||
{:else if activeSection === 'subscriptions'}
|
||||
<div class="section-header">
|
||||
<h2>{$t('subscription.subscriptions')}</h2>
|
||||
{#if user.role_id == 8}
|
||||
{#if hasPrivilige(user, PERMISSIONS.Super)}
|
||||
<button class="btn primary" on:click={() => openEditSubscriptionModal(null)}>
|
||||
<i class="fas fa-plus"></i>
|
||||
{$t('add_new')}
|
||||
@@ -285,7 +410,7 @@
|
||||
<summary class="accordion-header">
|
||||
{subscription.name}
|
||||
<span class="nav-badge"
|
||||
>{users.filter(
|
||||
>{members.filter(
|
||||
(/** @type{App.Locals['user']}*/ user) =>
|
||||
user.membership?.subscription_model?.name === subscription.name
|
||||
).length}</span
|
||||
@@ -328,7 +453,7 @@
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
{#if user.role_id == 8}
|
||||
{#if hasPrivilige(user, PERMISSIONS.Super)}
|
||||
<div class="button-group">
|
||||
<button
|
||||
class="btn primary"
|
||||
@@ -337,7 +462,7 @@
|
||||
<i class="fas fa-edit"></i>
|
||||
{$t('edit')}
|
||||
</button>
|
||||
{#if !users.some(/** @param{App.Locals['user']} user */ (user) => user.membership?.subscription_model?.id === subscription.id)}
|
||||
{#if !members.some(/** @param{App.Locals['user']} user */ (user) => user.membership?.subscription_model?.id === subscription.id)}
|
||||
<form
|
||||
method="POST"
|
||||
action="?/subscriptionDelete"
|
||||
@@ -415,7 +540,7 @@
|
||||
<Modal on:close={close}>
|
||||
<UserEditForm
|
||||
{form}
|
||||
role_id={user.role_id}
|
||||
editor={user}
|
||||
user={selectedUser}
|
||||
{subscriptions}
|
||||
{licence_categories}
|
||||
|
||||
Reference in New Issue
Block a user