add blocked user
All checks were successful
deploy / build-and-deploy (push) Successful in 15s

This commit is contained in:
2025-05-20 23:34:54 +02:00
parent ba1146facf
commit 8b18623232
14 changed files with 390 additions and 1 deletions

View File

@ -0,0 +1,101 @@
<script lang="ts">
import Input from '@components/input/Input.svelte';
import Textarea from '@components/input/Textarea.svelte';
import { confirmPopupState } from '@components/popup/ConfirmPopup.ts';
import type { BlockedUser } from '@app/admin/usersBlocked/types.ts';
import { blockedUserCreateOrEditPopupState } from '@app/admin/usersBlocked/state.ts';
import { onDestroy } from 'svelte';
// html bindings
let modal: HTMLDialogElement;
let modalForm: HTMLFormElement;
// states
let action = $state<'create' | 'edit' | null>(null);
let blockedUser = $state({} as BlockedUser);
let onUpdate = $state((_: BlockedUser) => {});
let submitEnabled = $derived(!!blockedUser.uuid);
// lifecycle
const cancel = blockedUserCreateOrEditPopupState.subscribe((value) => {
if (value && 'create' in value) {
action = 'create';
blockedUser = {
id: -1,
uuid: '',
comment: null
};
onUpdate = value?.create.onUpdate;
modal.show();
} else if (value && 'edit' in value) {
action = 'edit';
blockedUser = value.edit.blockedUser;
onUpdate = value.edit.onUpdate;
modal.show();
}
});
onDestroy(cancel);
// texts
const texts = {
create: {
title: 'Blockierten Nutzer erstellen',
buttonTitle: 'Erstellen',
confirmPopupTitle: 'Nutzer blockieren?',
confirmPopupMessage:
'Bist du sicher, dass der Nutzer blockiert werden soll?\nEin blockierter Nutzer kann sich nicht mehr registrieren.'
},
edit: {
title: 'Blockierten Nutzer bearbeiten',
buttonTitle: 'Speichern',
confirmPopupTitle: 'Änderungen speichern?',
confirmPopupMessage: 'Sollen die Änderungen gespeichert werden?'
},
null: {}
};
// callbacks
async function onSaveButtonClick(e: Event) {
e.preventDefault();
$confirmPopupState = {
title: texts[action!].confirmPopupTitle,
message: texts[action!].confirmPopupMessage,
onConfirm: () => {
modalForm.submit();
onUpdate(blockedUser);
}
};
}
function onCancelButtonClick(e: Event) {
e.preventDefault();
modalForm.submit();
}
</script>
<dialog class="modal" bind:this={modal}>
<form method="dialog" class="modal-box" bind:this={modalForm}>
<button class="btn btn-sm btn-circle btn-ghost absolute right-2 top-2" onclick={onCancelButtonClick}>✕</button>
<div class="space-y-5">
<h3 class="text-xt font-geist font-bold">{texts[action!].title}</h3>
<div>
<Input bind:value={blockedUser.uuid} label="UUID" required dynamicWidth />
<Textarea label="Kommentar" bind:value={blockedUser.comment} rows={3} dynamicWidth />
</div>
<div>
<button
class="btn btn-success"
class:disabled={!submitEnabled}
disabled={!submitEnabled}
onclick={onSaveButtonClick}>{texts[action!].buttonTitle}</button
>
<button class="btn btn-error" onclick={onCancelButtonClick}>Abbrechen</button>
</div>
</div>
</form>
<form method="dialog" class="modal-backdrop bg-[rgba(0,0,0,.3)]">
<button class="!cursor-default">close</button>
</form>
</dialog>

View File

@ -0,0 +1,26 @@
<script lang="ts">
import Icon from '@iconify/svelte';
import { addBlockedUser, fetchBlockedUsers } from '@app/admin/usersBlocked/actions.ts';
import { blockedUserCreateOrEditPopupState } from '@app/admin/usersBlocked/state.ts';
// lifecycle
$effect(() => {
fetchBlockedUsers();
});
// callbacks
async function onNewUserButtonClick() {
$blockedUserCreateOrEditPopupState = {
create: {
onUpdate: addBlockedUser
}
};
}
</script>
<div>
<button class="btn btn-soft w-full" onclick={() => onNewUserButtonClick()}>
<Icon icon="heroicons:plus-16-solid" />
<span>Neuer blockierter Nutzer</span>
</button>
</div>

View File

@ -0,0 +1,48 @@
<script lang="ts">
import Icon from '@iconify/svelte';
import CreateOrEditPopup from './CreateOrEditPopup.svelte';
import SortableTr from '@components/admin/table/SortableTr.svelte';
import SortableTh from '@components/admin/table/SortableTh.svelte';
import type { BlockedUser } from '@app/admin/usersBlocked/types.ts';
import { blockedUserCreateOrEditPopupState, blockedUsers } from '@app/admin/usersBlocked/state.ts';
import { editBlockedUser } from '@app/admin/usersBlocked/actions.ts';
// callbacks
async function onBlockedUserEditButtonClick(blockedUser: BlockedUser) {
$blockedUserCreateOrEditPopupState = {
edit: {
blockedUser: blockedUser,
onUpdate: editBlockedUser
}
};
}
</script>
<div class="h-screen overflow-x-auto">
<table class="table table-pin-rows">
<thead>
<SortableTr data={blockedUsers}>
<SortableTh style="width: 5%">#</SortableTh>
<SortableTh style="width: 20%" key="uuid">UUID</SortableTh>
<SortableTh style="width: 70%">Kommentar</SortableTh>
<SortableTh style="width: 5%"></SortableTh>
</SortableTr>
</thead>
<tbody>
{#each $blockedUsers as blockedUser, i (blockedUser)}
<tr class="hover:bg-base-200">
<td>{i + 1}</td>
<td>{blockedUser.uuid}</td>
<td>{blockedUser.comment}</td>
<td>
<button class="cursor-pointer" onclick={() => onBlockedUserEditButtonClick(blockedUser)}>
<Icon icon="heroicons:pencil-square" />
</button>
</td>
</tr>
{/each}
</tbody>
</table>
</div>
<CreateOrEditPopup />

View File

@ -0,0 +1,41 @@
import { actions } from 'astro:actions';
import { actionErrorPopup } from '@util/action.ts';
import { blockedUsers } from '@app/admin/usersBlocked/state.ts';
import type { BlockedUser } from '@app/admin/usersBlocked/types.ts';
export async function fetchBlockedUsers() {
const { data, error } = await actions.user.blocked();
if (error) {
actionErrorPopup(error);
return;
}
blockedUsers.set(data.blocked);
}
export async function addBlockedUser(blockedUser: BlockedUser) {
const { data, error } = await actions.user.addBlocked(blockedUser);
if (error) {
actionErrorPopup(error);
return;
}
blockedUsers.update((old) => {
old.push(Object.assign(blockedUser, { id: data.id }));
return old;
});
}
export async function editBlockedUser(blockedUser: BlockedUser) {
const { data, error } = await actions.user.editBlocked(blockedUser);
if (error) {
actionErrorPopup(error);
return;
}
blockedUsers.update((old) => {
const index = old.findIndex((a) => a.id == user.id);
old[index] = blockedUser;
return old;
});
}

View File

@ -0,0 +1,6 @@
import { writable } from 'svelte/store';
import type { BlockedUserCreateOrEditPopupState, BlockedUsers } from '@app/admin/usersBlocked/types.ts';
export const blockedUsers = writable<BlockedUsers>([]);
export const blockedUserCreateOrEditPopupState = writable<BlockedUserCreateOrEditPopupState>(null);

View File

@ -0,0 +1,9 @@
import { type ActionReturnType, actions } from 'astro:actions';
export type BlockedUsers = Exclude<ActionReturnType<typeof actions.user.blocked>['data'], undefined>['blocked'];
export type BlockedUser = BlockedUsers[0];
export type BlockedUserCreateOrEditPopupState =
| { create: { onUpdate: (blockedUser: BlockedUser) => void } }
| { edit: { blockedUser: BlockedUser; onUpdate: (blockedUser: BlockedUser) => void } }
| null;