wip content index
This commit is contained in:
@@ -45,5 +45,15 @@ return [
|
|||||||
\Lucent\Schema\Ui\Slug::class,
|
\Lucent\Schema\Ui\Slug::class,
|
||||||
\Lucent\Schema\Ui\Text::class,
|
\Lucent\Schema\Ui\Text::class,
|
||||||
\Lucent\Schema\Ui\Textarea::class
|
\Lucent\Schema\Ui\Textarea::class
|
||||||
|
],
|
||||||
|
"renderers" => [
|
||||||
|
"row" => [
|
||||||
|
"file" => \Lucent\Schema\Renderer\Row\File::class,
|
||||||
|
"slug" => \Lucent\Schema\Renderer\Row\Text::class,
|
||||||
|
"text" => \Lucent\Schema\Renderer\Row\Text::class,
|
||||||
|
"checkbox" => \Lucent\Schema\Renderer\Row\Text::class,
|
||||||
|
"number" => \Lucent\Schema\Renderer\Row\Text::class,
|
||||||
|
"rich" => \Lucent\Schema\Renderer\Row\Text::class,
|
||||||
|
]
|
||||||
]
|
]
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -6,10 +6,14 @@ import Mustache from "mustache";
|
|||||||
import 'htmx.org';
|
import 'htmx.org';
|
||||||
import {dropdown} from "./components/dropdown.js";
|
import {dropdown} from "./components/dropdown.js";
|
||||||
import {colorPicker} from "./recordEditor/colorPicker.js";
|
import {colorPicker} from "./recordEditor/colorPicker.js";
|
||||||
|
import {sortReferences} from "./recordEditor/sortReferences.js";
|
||||||
|
import {recordDialog} from "./recordEditor/recordDialog.js";
|
||||||
|
|
||||||
addEventListener("load", (event) => {
|
addEventListener("load", (event) => {
|
||||||
dropdown()
|
dropdown()
|
||||||
colorPicker()
|
colorPicker()
|
||||||
|
sortReferences()
|
||||||
|
recordDialog()
|
||||||
});
|
});
|
||||||
|
|
||||||
Mustache.escape = function (value) {
|
Mustache.escape = function (value) {
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
import axios from "axios";
|
||||||
|
|
||||||
|
export function recordDialog() {
|
||||||
|
|
||||||
|
document.querySelectorAll("[data-open-modal]").forEach(el => {
|
||||||
|
const schema = el.dataset.openModal
|
||||||
|
el.addEventListener("click", e => {
|
||||||
|
load(schema)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function load(schema) {
|
||||||
|
axios
|
||||||
|
.get("/lucent/content/" + schema)
|
||||||
|
.then((response) => {
|
||||||
|
|
||||||
|
const dialogWrapperEl = document.createElement("div");
|
||||||
|
dialogWrapperEl.innerHTML = response.data;
|
||||||
|
document.body.appendChild(dialogWrapperEl);
|
||||||
|
const dialogEl = dialogWrapperEl.querySelector("dialog");
|
||||||
|
dialogEl.showModal();
|
||||||
|
dialogWrapperEl.querySelector(".close").addEventListener("click", e => dialogEl.close());
|
||||||
|
|
||||||
|
dialogEl.addEventListener("close", (event) => {
|
||||||
|
dialogWrapperEl.remove();
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((error) => console.log(error));
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import Sortable from "sortablejs";
|
||||||
|
export function sortReferences() {
|
||||||
|
document.querySelectorAll(".color-picker").forEach(el => {
|
||||||
|
|
||||||
|
let options = {
|
||||||
|
animation: 150, // ms, animation speed moving items when sorting, `0` — without animation
|
||||||
|
easing: "cubic-bezier(1, 0, 0, 1)",
|
||||||
|
direction: 'vertical',
|
||||||
|
onUpdate: function (/**Event*/ evt) {
|
||||||
|
// dispatch("update", {
|
||||||
|
// source: evt.oldIndex,
|
||||||
|
// target: evt.newIndex,
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Sortable.create(el, options);
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -11,7 +11,8 @@
|
|||||||
/* max-width: 128px; */
|
/* max-width: 128px; */
|
||||||
max-height: 24px;
|
max-height: 24px;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
/* white-space: nowrap; */
|
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
/* white-space: nowrap; */
|
||||||
|
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -55,7 +55,11 @@
|
|||||||
border: none;
|
border: none;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
|
&.field-ui-number,&.field-ui-slug,&.field-ui-text,&.field-ui-rich,&.field-ui-url{
|
||||||
|
max-height: 24px;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
//img{
|
//img{
|
||||||
// width: 48px;
|
// width: 48px;
|
||||||
//}
|
//}
|
||||||
|
|||||||
@@ -28,7 +28,7 @@
|
|||||||
return strtoupper($segs[0][0]).strtoupper($segs[0][1]);
|
return strtoupper($segs[0][0]).strtoupper($segs[0][1]);
|
||||||
};
|
};
|
||||||
|
|
||||||
$name = $user["name"];
|
$name = (string)data_get($user,"name");
|
||||||
$charIndex = ord($name[1]) + strlen($name);
|
$charIndex = ord($name[1]) + strlen($name);
|
||||||
$colorIndex = $charIndex % 19;
|
$colorIndex = $charIndex % 19;
|
||||||
$bgColor = $colors[$colorIndex];
|
$bgColor = $colors[$colorIndex];
|
||||||
@@ -39,5 +39,5 @@
|
|||||||
title="{{$name}}"
|
title="{{$name}}"
|
||||||
style="background-color:{{$bgColor}};height: {{$side}}px;width: {{$side}}px; font-size:{{$side / 2}}px"
|
style="background-color:{{$bgColor}};height: {{$side}}px;width: {{$side}}px; font-size:{{$side / 2}}px"
|
||||||
>
|
>
|
||||||
<div class="avatar__letters">{{$initials($user["name"])}}</div>
|
<div class="avatar__letters">{{$initials($name)}}</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
<div class="checkbox-wrapper">
|
||||||
|
<input id="c1-13" type="checkbox" value="{{$value}}" {{$indeterminate ?? false ? "indeterminate" : ""}} {{$checked ?? false ? "checked" : ""}} />
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
<dialog id="dialog-{{$schema->name}}">
|
||||||
|
|
||||||
|
|
||||||
|
@if($schema)
|
||||||
|
<div class="dialog-header">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="button"
|
||||||
|
disabled
|
||||||
|
>
|
||||||
|
Insert
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="button"
|
||||||
|
disabled
|
||||||
|
>
|
||||||
|
Replace
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<div class="hide">
|
||||||
|
<span class="number-of-records-selected"></span> records selected
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
class="button close"
|
||||||
|
aria-label="Close"
|
||||||
|
>
|
||||||
|
<x-lucent::icon icon="close">
|
||||||
|
|
||||||
|
</x-lucent::icon>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="dialog-body">
|
||||||
|
@include("lucent::records.index")
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@endif
|
||||||
|
</dialog>
|
||||||
@@ -18,5 +18,6 @@
|
|||||||
@include("lucent::records-editor.fields", ["field" => $field])
|
@include("lucent::records-editor.fields", ["field" => $field])
|
||||||
@endforeach
|
@endforeach
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@endsection
|
@endsection
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,31 @@
|
|||||||
@php
|
@php
|
||||||
$references = $graph->edges
|
$references = $graph->edges
|
||||||
->filter(fn($edge) => $edge->field === $field->name && $edge->source === $record->id)
|
->filter(fn($edge) => $edge->field === $field->name && $edge->source === $record->id)
|
||||||
->map(fn($edge) => $graph->records->firstWhere("id", $edge->target));
|
->map(fn($edge) => $graph->records->firstWhere("id", $edge->target));
|
||||||
|
|
||||||
|
$collectionSchemas = $schemas->whereIn("name",$field->collections);
|
||||||
@endphp
|
@endphp
|
||||||
|
|
||||||
@if ($references->isNotEmpty())
|
@if(count($field->collections) === 1)
|
||||||
{{--<Sortable sortableClass="mt-3" on:update={reorder}>--}}
|
<button class="button" data-open-modal="{{$field->collections[0]}}">Browse</button>
|
||||||
@foreach($references as $reference)
|
@else
|
||||||
<!--This div helps the sorting thing-->
|
<x-lucent::dropdown>
|
||||||
<div>
|
Browse
|
||||||
@include("lucent::records-editor.fields.file.preview")
|
<x-slot:items>
|
||||||
</div>
|
@foreach($collectionSchemas as $collectionSchema)
|
||||||
@endforeach
|
<a class="dropdown-item" data-open-modal="{{$collectionSchema->name}}" href="/">{{$collectionSchema->label}}</a>
|
||||||
<!--</Sortable>-->
|
@endforeach
|
||||||
|
</x-slot:items>
|
||||||
|
</x-lucent::dropdown>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
@if ($references->isNotEmpty())
|
||||||
|
<div class="sortable-container mt-3">
|
||||||
|
@foreach($references as $reference)
|
||||||
|
<!--This div helps the sorting thing-->
|
||||||
|
<div>
|
||||||
|
@include("lucent::records-editor.fields.file.preview", ["record" => $reference])
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
@php
|
@php
|
||||||
$schema = $channel->schemas->firstWhere("name",$reference->schema);
|
$reference = $record;
|
||||||
|
$schema = $channel->schemas->firstWhere("name",$record->schema);
|
||||||
@endphp
|
@endphp
|
||||||
|
|
||||||
<div class="preview-file">
|
<div class="preview-file">
|
||||||
<div style="display: flex;align-items: center;gap: 10px;">
|
<div style="display: flex;align-items: center;gap: 10px;">
|
||||||
<div class="image">
|
<div class="image">
|
||||||
@include("lucent::records-editor.fields.file.thumb", ["size" => "small", "record" => $reference])
|
@include("lucent::records-editor.fields.file.thumb", ["size" => "small"])
|
||||||
</div>
|
</div>
|
||||||
<div class="title">
|
<div class="title">
|
||||||
<div>
|
<div>
|
||||||
<a class="record-title" href="{{lucent_url("records")}}/{{$record->id}}">
|
<a class="record-title" href="{{lucent_url("records")}}/{{$record->id}}">
|
||||||
{{$viewModel->getRecordName($reference)}}
|
{{$viewModel->getRecordName($record)}}
|
||||||
</a>
|
</a>
|
||||||
<small class="d-block">
|
<small class="d-block">
|
||||||
from {{$schema->label}}
|
from {{$schema->label}}
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
|
||||||
|
<div class="">
|
||||||
|
<div class="{{$inModal ? 'mt-0' : 'mt-5'}}">
|
||||||
|
<h3 class="header-normal mb-5 ">
|
||||||
|
{{$schema->label}}
|
||||||
|
</h3>
|
||||||
|
{{-- {#if selected.length > 0 && !inModal && isWritable}--}}
|
||||||
|
{{-- <ActionsOnSelected {schema} {selected} {filter}/>--}}
|
||||||
|
{{-- {:else}--}}
|
||||||
|
{{-- <Tools--}}
|
||||||
|
{{-- bind:schema--}}
|
||||||
|
{{-- bind:records--}}
|
||||||
|
{{-- {systemFields}--}}
|
||||||
|
{{-- {sortParam}--}}
|
||||||
|
{{-- {sortField}--}}
|
||||||
|
{{-- {operators}--}}
|
||||||
|
{{-- {filter}--}}
|
||||||
|
{{-- {graph}--}}
|
||||||
|
{{-- {inModal}--}}
|
||||||
|
{{-- {modalUrl}--}}
|
||||||
|
{{-- {isWritable}--}}
|
||||||
|
{{-- on:refresh={refresh}--}}
|
||||||
|
{{-- />--}}
|
||||||
|
{{-- {/if}--}}
|
||||||
|
@include("lucent::records.table")
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- <Pagination--}}
|
||||||
|
{{-- {limit}--}}
|
||||||
|
{{-- {skip}--}}
|
||||||
|
{{-- {total}--}}
|
||||||
|
{{-- on:refresh={refresh}--}}
|
||||||
|
{{-- {inModal}--}}
|
||||||
|
{{-- {modalUrl}--}}
|
||||||
|
{{-- />--}}
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
@extends("lucent::layouts.channel")
|
||||||
|
|
||||||
|
@section("content")
|
||||||
|
@include("lucent::records.index")
|
||||||
|
|
||||||
|
@endsection
|
||||||
|
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
@foreach($schema->visible as $visibleColumn)
|
||||||
|
@php
|
||||||
|
$schemaField = $schema->fields->firstWhere("name", $visibleColumn);
|
||||||
|
@endphp
|
||||||
|
<td class="field-ui-{{$schemaField->info->name ?? $visibleColumn}} {{$visibleColumn === $sortField->name ? "is-sort" : ""}}">
|
||||||
|
@if(in_array($visibleColumn ,["_sys.createdBy","_sys.updatedBy"]))
|
||||||
|
<x-lucent::avatar side="24" :user="$users->firstWhere('id',$record->_sys->createdBy)"></x-lucent::avatar>
|
||||||
|
@elseif($visibleColumn === "_sys.status")
|
||||||
|
@include("lucent::records-editor.status",[ "status" => $record->status])
|
||||||
|
@else
|
||||||
|
{!! $viewModel->renderRow($record,$schemaField)!!}
|
||||||
|
@endif
|
||||||
|
</td>
|
||||||
|
@endforeach
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
@php
|
||||||
|
|
||||||
|
@endphp
|
||||||
|
|
||||||
|
<div class="table mt-5 ">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
@if($isWritable)
|
||||||
|
<th>
|
||||||
|
<x-lucent::checkbox value=""></x-lucent::checkbox>
|
||||||
|
</th>
|
||||||
|
@endif
|
||||||
|
@foreach($schema->visible as $visibleColumn)
|
||||||
|
@php
|
||||||
|
$schemaField = $schema->fields->firstWhere("name", $visibleColumn);
|
||||||
|
if(empty($schemaField)){
|
||||||
|
$schemaField = collect($systemFields)->firstWhere("name", str_replace("_sys.", "",$visibleColumn) );
|
||||||
|
}
|
||||||
|
@endphp
|
||||||
|
<th
|
||||||
|
class="field-ui-{{$schemaField->info->name ?? $schemaField->ui}} {{$schemaField->name === $sortField->name ? "is-sort" : ""}}"
|
||||||
|
scope="col"
|
||||||
|
title={{$schemaField->help ?? ""}}
|
||||||
|
>{{$schemaField->label}}</th
|
||||||
|
>
|
||||||
|
@endforeach
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
@foreach($records as $record)
|
||||||
|
<tr>
|
||||||
|
<td class="title-td">
|
||||||
|
<div
|
||||||
|
class="title-td-contents"
|
||||||
|
>
|
||||||
|
@if($isWritable)
|
||||||
|
<x-lucent::checkbox :value="$record->id"></x-lucent::checkbox>
|
||||||
|
|
||||||
|
@endif
|
||||||
|
@if($record->_file?->path)
|
||||||
|
<div class="file-table-row">
|
||||||
|
@include("lucent::records-editor.fields.file.thumb", ["size" => "small"])
|
||||||
|
<div>
|
||||||
|
@if($record->status === "draft")
|
||||||
|
<span style="text-transform: uppercase;font-size:10px">{{$record->status}}</span>
|
||||||
|
@endif
|
||||||
|
<a
|
||||||
|
href="{{lucent_url("records")}}/{{$record->id}}"
|
||||||
|
target={{$inModal ? "_blank" : "_self"}}
|
||||||
|
>
|
||||||
|
{{ $viewModel->getRecordName($record)}}
|
||||||
|
</a>
|
||||||
|
<span>{{ (int)($record->_file->size / 1024) }}kB</span>
|
||||||
|
|
||||||
|
@if($record->_file->width > 0)
|
||||||
|
<span>{{$record->_file->width . "x" . $record->_file->height}}</span>
|
||||||
|
@endif
|
||||||
|
<a
|
||||||
|
href="{{lucent_file($record)}}"
|
||||||
|
target="_blank"
|
||||||
|
>
|
||||||
|
Download
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<a
|
||||||
|
href="{{lucent_url("records")}}/{{$record->id}}"
|
||||||
|
target={{$inModal ? "_blank" : "_self"}}
|
||||||
|
>
|
||||||
|
@if($record->status === "draft")
|
||||||
|
<span style="text-transform: uppercase;font-size:10px">{{$record->status}}</span>
|
||||||
|
@endif
|
||||||
|
{{$viewModel->getRecordName($record)}}
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
@include("lucent::records.row")
|
||||||
|
<td>
|
||||||
|
<x-lucent::avatar side="24"
|
||||||
|
:user="$users->firstWhere('id',$record->_sys->createdBy)"></x-lucent::avatar>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
@endforeach
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
@@ -79,14 +79,15 @@ class RecordController extends Controller
|
|||||||
->runWithCount();
|
->runWithCount();
|
||||||
|
|
||||||
|
|
||||||
$records = $graph->getRootRecords()->toArray();
|
|
||||||
|
|
||||||
$data = [
|
$data = [
|
||||||
"schemas" => $this->channelService->channel->schemas,
|
"schemas" => $this->channelService->channel->schemas,
|
||||||
"schema" => $schema,
|
"schema" => $schema,
|
||||||
"users" => $users,
|
"users" => $users,
|
||||||
"records" => $records,
|
"records" => $graph->tree(),
|
||||||
"graph" => toArray($graph),
|
"graph" => toArray($graph),
|
||||||
|
"visibleFields" => array_values(System::list()),
|
||||||
"systemFields" => array_values(System::list()),
|
"systemFields" => array_values(System::list()),
|
||||||
"operators" => $this->operatorRegistry->all(),
|
"operators" => $this->operatorRegistry->all(),
|
||||||
"sortParam" => $sort,
|
"sortParam" => $sort,
|
||||||
@@ -104,9 +105,10 @@ class RecordController extends Controller
|
|||||||
if (str_starts_with(config("lucent.url"), "https")) {
|
if (str_starts_with(config("lucent.url"), "https")) {
|
||||||
$data["modalUrl"] = str_replace("http://", "https://", $request->fullUrl());
|
$data["modalUrl"] = str_replace("http://", "https://", $request->fullUrl());
|
||||||
}
|
}
|
||||||
return $data;
|
return view("lucent::records-editor.dialog", $data)->render();
|
||||||
}
|
}
|
||||||
$data["inModal"] = false;
|
$data["inModal"] = false;
|
||||||
|
return view("lucent::records.list", $data);
|
||||||
return $this->svelte->render(
|
return $this->svelte->render(
|
||||||
layout: "channel",
|
layout: "channel",
|
||||||
view: "contentIndex",
|
view: "contentIndex",
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Schema\Renderer\Row;
|
||||||
|
|
||||||
|
use Lucent\Record\QueryRecord;
|
||||||
|
use Lucent\Schema\FieldInterface;
|
||||||
|
|
||||||
|
class File implements IRowRenderer
|
||||||
|
{
|
||||||
|
|
||||||
|
public function __invoke(QueryRecord $record, FieldInterface $field): string
|
||||||
|
{
|
||||||
|
$reference = data_get($record,"_children.".$field->name);
|
||||||
|
if(!isset($reference[0])){
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return view("lucent::records-editor.fields.file.thumb",["size" => "tiny", "record" => $reference[0]])->render();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Schema\Renderer\Row;
|
||||||
|
|
||||||
|
use Lucent\Record\QueryRecord;
|
||||||
|
use Lucent\Schema\FieldInterface;
|
||||||
|
|
||||||
|
interface IRowRenderer
|
||||||
|
{
|
||||||
|
public function __invoke(QueryRecord $record, FieldInterface $field):string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Lucent\Schema\Renderer\Row;
|
||||||
|
|
||||||
|
use Lucent\Record\QueryRecord;
|
||||||
|
use Lucent\Schema\FieldInterface;
|
||||||
|
|
||||||
|
class Text implements IRowRenderer
|
||||||
|
{
|
||||||
|
|
||||||
|
public function __invoke(QueryRecord $record, FieldInterface $field): string
|
||||||
|
{
|
||||||
|
return data_get($record,"data.".$field->name,"");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -37,4 +37,11 @@ class ViewModel
|
|||||||
$m = new Mustache_Engine(array('entity_flags' => ENT_QUOTES));
|
$m = new Mustache_Engine(array('entity_flags' => ENT_QUOTES));
|
||||||
return $m->render($schema->cardTitle, $record->data);
|
return $m->render($schema->cardTitle, $record->data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function renderRow(QueryRecord $record, FieldInterface $field): string
|
||||||
|
{
|
||||||
|
$renderers = config("lucent.renderers.row");
|
||||||
|
return (new $renderers[$field->info->name])($record, $field);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user