- Implement ESLint with @antfu/eslint-config and apply consistent formatting across the codebase.
- Refactor `useTasks` to improve task fetching, creation, and updating logic. - Enhance `TodoItem` and `ListScreen` with improved bindings, sorting, and category handling. - Update dependencies and adjust task grouping in various components.
This commit is contained in:
6
eslint.config.js
Normal file
6
eslint.config.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import antfu from '@antfu/eslint-config'
|
||||||
|
|
||||||
|
export default antfu({
|
||||||
|
formatters: true,
|
||||||
|
vue: true,
|
||||||
|
})
|
||||||
@@ -37,7 +37,10 @@
|
|||||||
"tailwindcss": "^4.2.0",
|
"tailwindcss": "^4.2.0",
|
||||||
"typescript": "~5.9.3",
|
"typescript": "~5.9.3",
|
||||||
"vite": "^7.3.1",
|
"vite": "^7.3.1",
|
||||||
"vue-tsc": "^2.1.10"
|
"vue-tsc": "^2.1.10",
|
||||||
|
"@antfu/eslint-config": "^7.4.3",
|
||||||
|
"eslint": "^9.39.2",
|
||||||
|
"eslint-plugin-format": "^1.4.0"
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@10.27.0+sha512.72d699da16b1179c14ba9e64dc71c9a40988cbdc65c264cb0e489db7de917f20dcf4d64d8723625f2969ba52d4b7e2a1170682d9ac2a5dcaeaab732b7e16f04a"
|
"packageManager": "pnpm@10.27.0+sha512.72d699da16b1179c14ba9e64dc71c9a40988cbdc65c264cb0e489db7de917f20dcf4d64d8723625f2969ba52d4b7e2a1170682d9ac2a5dcaeaab732b7e16f04a"
|
||||||
}
|
}
|
||||||
2705
pnpm-lock.yaml
generated
2705
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
16
src/App.vue
16
src/App.vue
@@ -1,14 +1,17 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { PhCheckSquareOffset, PhListChecks, PhSliders } from '@phosphor-icons/vue';
|
import { PhCheckSquareOffset, PhListChecks, PhSliders } from '@phosphor-icons/vue'
|
||||||
import { useRouter } from 'vue-router';
|
import { computed, onMounted } from 'vue'
|
||||||
import { computed } from 'vue';
|
import { useRouter } from 'vue-router'
|
||||||
const router = useRouter();
|
import { useTasks } from './composables/useTasks.ts'
|
||||||
const currentPath = computed(() => router.currentRoute.value.path);
|
|
||||||
|
const { fetchTasks } = useTasks()
|
||||||
|
const router = useRouter()
|
||||||
|
const currentPath = computed(() => router.currentRoute.value.path)
|
||||||
|
onMounted(fetchTasks)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="overflow-hidden">
|
<div class="overflow-hidden">
|
||||||
|
|
||||||
<main class="pb-40 overflow-y-scroll h-screen">
|
<main class="pb-40 overflow-y-scroll h-screen">
|
||||||
<RouterView />
|
<RouterView />
|
||||||
</main>
|
</main>
|
||||||
@@ -26,5 +29,4 @@ const currentPath = computed(() => router.currentRoute.value.path);
|
|||||||
</RouterLink>
|
</RouterLink>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,41 +1,46 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { PhCheckSquare, PhDotsThree, PhFlag, PhPause, PhPlay, PhSquare, PhX } from '@phosphor-icons/vue';
|
import type { Task } from '../types.ts'
|
||||||
import { TaskStatus, Task } from '../types.ts';
|
import { PhCheckSquare, PhDotsThree, PhFlag, PhPause, PhPlay, PhSquare, PhX } from '@phosphor-icons/vue'
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon'
|
||||||
import { computed, ref } from 'vue';
|
import { computed, ref } from 'vue'
|
||||||
import { useTasks } from '../composables/useTasks.ts';
|
import { useTasks } from '../composables/useTasks.ts'
|
||||||
|
import { TaskStatus } from '../types.ts'
|
||||||
|
|
||||||
const {updateTask} = useTasks()
|
const { task } = defineProps<{ task: Task }>()
|
||||||
|
|
||||||
const {task} = defineProps<{task: Task}>()
|
const { updateTask } = useTasks()
|
||||||
|
|
||||||
const dueColor = computed(() => {
|
const dueColor = computed(() => {
|
||||||
const dueDiff = task.dueDate ? DateTime.fromMillis(task.dueDate).diffNow('days').days : undefined;
|
const dueDiff = task.dueDate ? DateTime.fromMillis(task.dueDate).diffNow('days').days : undefined
|
||||||
if (!dueDiff) return '';
|
if (!dueDiff)
|
||||||
|
return ''
|
||||||
if (dueDiff < 0) {
|
if (dueDiff < 0) {
|
||||||
return 'text-error'
|
return 'text-error'
|
||||||
} else if (dueDiff < 2) {
|
}
|
||||||
return 'text-warning';
|
else if (dueDiff < 2) {
|
||||||
} else if (dueDiff < 7) {
|
return 'text-warning'
|
||||||
return 'text-success';
|
}
|
||||||
} else {
|
else if (dueDiff < 7) {
|
||||||
return 'text-neutral';
|
return 'text-success'
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return 'text-neutral'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
const statusSelectVisible = ref(false);
|
const statusSelectVisible = ref(false)
|
||||||
|
|
||||||
const handleClick = async(update: Partial<Task>) => {
|
async function handleClick(update: Partial<Task>) {
|
||||||
updateTask({...task, ...update})
|
updateTask({ ...task, ...update })
|
||||||
statusSelectVisible.value = false;
|
statusSelectVisible.value = false
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<li class="list-row" >
|
<li class="list-row">
|
||||||
<div class="flex items-center justify-center">
|
<div class="flex items-center justify-center">
|
||||||
<button class="btn btn-square btn-ghost" @click="statusSelectVisible = !statusSelectVisible">
|
<button class="btn btn-square btn-ghost" @click="statusSelectVisible = !statusSelectVisible">
|
||||||
<PhX :size="20" v-if="statusSelectVisible" />
|
<PhX v-if="statusSelectVisible" :size="20" />
|
||||||
<template v-else>
|
<template v-else>
|
||||||
<PhSquare v-if="task.status === TaskStatus.WAIT" :size="20" />
|
<PhSquare v-if="task.status === TaskStatus.WAIT" :size="20" />
|
||||||
<PhCheckSquare v-else-if="task.status === TaskStatus.DONE" :size="20" weight="fill" class="text-success" />
|
<PhCheckSquare v-else-if="task.status === TaskStatus.DONE" :size="20" weight="fill" class="text-success" />
|
||||||
@@ -46,28 +51,30 @@ const handleClick = async(update: Partial<Task>) => {
|
|||||||
<Transition>
|
<Transition>
|
||||||
<div v-if="statusSelectVisible" class="">
|
<div v-if="statusSelectVisible" class="">
|
||||||
<template v-if="task.status !== TaskStatus.WIP">
|
<template v-if="task.status !== TaskStatus.WIP">
|
||||||
<button v-if="task.status !== TaskStatus.DONE" class="btn btn-square btn-ghost" @click="handleClick({status: TaskStatus.DONE})">
|
<button v-if="task.status !== TaskStatus.DONE" class="btn btn-square btn-ghost" @click="handleClick({ status: TaskStatus.DONE })">
|
||||||
<PhCheckSquare :size="24" weight="regular" class="text-success" />
|
<PhCheckSquare :size="24" weight="regular" class="text-success" />
|
||||||
</button>
|
</button>
|
||||||
<button v-if="task.status !== TaskStatus.WAIT" class="btn btn-square btn-ghost" @click="handleClick({status: TaskStatus.WAIT})">
|
<button v-if="task.status !== TaskStatus.WAIT" class="btn btn-square btn-ghost" @click="handleClick({ status: TaskStatus.WAIT })">
|
||||||
<PhSquare :size="24" weight="regular" />
|
<PhSquare :size="24" weight="regular" />
|
||||||
</button>
|
</button>
|
||||||
<button v-if="task.status !== TaskStatus.FLAG" class="btn btn-square btn-ghost" @click="handleClick({status: TaskStatus.FLAG})">
|
<button v-if="task.status !== TaskStatus.FLAG" class="btn btn-square btn-ghost" @click="handleClick({ status: TaskStatus.FLAG })">
|
||||||
<PhFlag :size="24" weight="fill" class="text-warning" />
|
<PhFlag :size="24" weight="fill" class="text-warning" />
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-square btn-ghost" @click="handleClick({status: TaskStatus.WIP})">
|
<button class="btn btn-square btn-ghost" @click="handleClick({ status: TaskStatus.WIP })">
|
||||||
<PhPlay :size="24" weight="fill" class="text-info" />
|
<PhPlay :size="24" weight="fill" class="text-info" />
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
<button v-else class="btn btn-square btn-ghost" @click="handleClick({status: TaskStatus.WAIT})">
|
<button v-else class="btn btn-square btn-ghost" @click="handleClick({ status: TaskStatus.WAIT })">
|
||||||
<PhPause :size="24" weight="fill" class="text-info" />
|
<PhPause :size="24" weight="fill" class="text-info" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</Transition>
|
</Transition>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col justify-center">
|
<div class="flex flex-col justify-center">
|
||||||
<div>{{task.title}}</div>
|
<div>{{ task.id_ }} {{ task.title }}</div>
|
||||||
<div :class="dueColor" v-if="task.dueDate">{{DateTime.fromMillis(task.dueDate).toFormat('dd/MM/yyyy')}}</div>
|
<div v-if="task.dueDate" :class="dueColor">
|
||||||
|
{{ DateTime.fromMillis(task.dueDate).toFormat('dd/MM/yyyy') }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<button class="btn btn-square btn-ghost">
|
<button class="btn btn-square btn-ghost">
|
||||||
<PhDotsThree :size="24" weight="regular" />
|
<PhDotsThree :size="24" weight="regular" />
|
||||||
|
|||||||
@@ -1,89 +1,88 @@
|
|||||||
import { ref } from 'vue';
|
import type { Task } from '../types.ts'
|
||||||
import { useApi } from './useApi.ts';
|
import { useArrayUnique } from '@vueuse/core'
|
||||||
import { Task } from '../types.ts';
|
import { ref } from 'vue'
|
||||||
import { useArrayReduce, useArrayUnique } from '@vueuse/core'
|
import { useApi } from './useApi.ts'
|
||||||
|
|
||||||
const tasks = ref<Task[]>([]);
|
const tasks = ref<Task[]>([])
|
||||||
const isLoading = ref(false);
|
const isLoading = ref(false)
|
||||||
const error = ref<string | null>(null);
|
const error = ref<string | null>(null)
|
||||||
|
|
||||||
export function useTasks() {
|
export function useTasks() {
|
||||||
const api = useApi();
|
const api = useApi()
|
||||||
|
|
||||||
const fetchTasks = async (force = false) => {
|
const fetchTasks = async (force = false) => {
|
||||||
if (tasks.value.length > 0 && !force) return;
|
if (tasks.value.length > 0 && !force)
|
||||||
|
return
|
||||||
|
|
||||||
isLoading.value = true;
|
isLoading.value = true
|
||||||
error.value = null;
|
error.value = null
|
||||||
try {
|
try {
|
||||||
const data = await api.get('e5880167-9322-4d7b-8a38-e06bae8a7734/list').then((res) => res.json());
|
const data = await api.get('e5880167-9322-4d7b-8a38-e06bae8a7734/list').then(res => res.json())
|
||||||
tasks.value = data.tasks ?? [];
|
tasks.value = data.tasks ?? []
|
||||||
} catch (e: any) {
|
}
|
||||||
error.value = e.message || 'Failed to fetch tasks';
|
catch (e: any) {
|
||||||
console.error('Error fetching tasks:', e);
|
error.value = e.message || 'Failed to fetch tasks'
|
||||||
} finally {
|
console.error('Error fetching tasks:', e)
|
||||||
isLoading.value = false;
|
}
|
||||||
|
finally {
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
const createTask = async (taskData: Partial<Task>) => {
|
const createTask = async (taskData: Partial<Task>) => {
|
||||||
isLoading.value = true;
|
isLoading.value = true
|
||||||
error.value = null;
|
error.value = null
|
||||||
try {
|
try {
|
||||||
// Get next ID as per current logic in CreateScreen.vue
|
// Get next ID as per current logic in CreateScreen.vue
|
||||||
const nextId = await api.get('d49dde4c-530d-46ee-8205-d1357563ac16')
|
const nextId = tasks.value.sort((a, b) => b.id_ - a.id_).reduce((acc, task) => {
|
||||||
.then((response) => response.json())
|
if (task.id_ === acc + 1)
|
||||||
.then((json) => json.nextId as number)
|
return acc + 1
|
||||||
.catch(() => null);
|
return task.id_ + 1
|
||||||
|
}, 0)
|
||||||
if (!nextId) throw new Error('Could not get next task ID');
|
|
||||||
|
|
||||||
const newTask: Partial<Task> = {
|
const newTask: Partial<Task> = {
|
||||||
...taskData,
|
...taskData,
|
||||||
id_: nextId,
|
id_: nextId,
|
||||||
logs: [],
|
logs: [],
|
||||||
archived: false,
|
archived: false,
|
||||||
};
|
}
|
||||||
|
|
||||||
const data = await api.put('e5880167-9322-4d7b-8a38-e06bae8a7734/list', { tasks: [newTask] }).then((res) => res.json());
|
const data = await api.put('e5880167-9322-4d7b-8a38-e06bae8a7734/list', { tasks: [newTask] }).then(res => res.json())
|
||||||
if (data.tasks) {
|
if (data.tasks) {
|
||||||
tasks.value = data.tasks;
|
tasks.value = data.tasks
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (e: any) {
|
||||||
|
error.value = e.message || 'Failed to create task'
|
||||||
|
console.error('Error creating task:', e)
|
||||||
|
throw e
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
isLoading.value = false
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
|
||||||
error.value = e.message || 'Failed to create task';
|
|
||||||
console.error('Error creating task:', e);
|
|
||||||
throw e;
|
|
||||||
} finally {
|
|
||||||
isLoading.value = false;
|
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
const updateTask = async (task: Task) => {
|
const updateTask = async (task: Task) => {
|
||||||
console.log('updateTask',task);
|
console.log('updateTask', task)
|
||||||
isLoading.value = true;
|
isLoading.value = true
|
||||||
error.value = null;
|
error.value = null
|
||||||
try {
|
try {
|
||||||
const data = await api.put('e5880167-9322-4d7b-8a38-e06bae8a7734/list', { tasks: [task] }).then((res) => res.json());
|
const data = await api.put('e5880167-9322-4d7b-8a38-e06bae8a7734/list', { tasks: [task] }).then(res => res.json())
|
||||||
if (data.tasks) {
|
if (data.tasks) {
|
||||||
tasks.value = data.tasks;
|
tasks.value = data.tasks
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
}
|
||||||
error.value = e.message || 'Failed to update task';
|
catch (e: any) {
|
||||||
console.error('Error updating task:', e);
|
error.value = e.message || 'Failed to update task'
|
||||||
throw e;
|
console.error('Error updating task:', e)
|
||||||
} finally {
|
throw e
|
||||||
isLoading.value = false;
|
}
|
||||||
|
finally {
|
||||||
|
isLoading.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const tasksByCategory = useArrayReduce(tasks.value.sort((a,b) => a.id_ - b.id_), (acc, task) => {
|
const categories = useArrayUnique(tasks.value.map(task => task.tag))
|
||||||
const tag = task.tag ?? 'Uncategorized';
|
|
||||||
acc[tag] = acc[tag] ?? [];
|
|
||||||
acc[tag].push(task);
|
|
||||||
return acc;
|
|
||||||
}, {} as Record<string, Task[]>)
|
|
||||||
|
|
||||||
const categories = useArrayUnique(Object.keys(tasksByCategory.value))
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
tasks,
|
tasks,
|
||||||
@@ -93,5 +92,5 @@ export function useTasks() {
|
|||||||
createTask,
|
createTask,
|
||||||
updateTask,
|
updateTask,
|
||||||
categories,
|
categories,
|
||||||
};
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { router } from '../router.ts';
|
import type { Task } from '../types.ts'
|
||||||
import { useTasks } from '../composables/useTasks.ts';
|
import { useTasks } from '../composables/useTasks.ts'
|
||||||
import { Task } from '../types.ts';
|
import { router } from '../router.ts'
|
||||||
|
|
||||||
const { createTask } = useTasks();
|
const { createTask, tasks } = useTasks()
|
||||||
|
|
||||||
const handleSubmit = async(e: Event) => {
|
async function handleSubmit(e: Event) {
|
||||||
const data = new FormData(e.target as HTMLFormElement);
|
const data = new FormData(e.target as HTMLFormElement)
|
||||||
const task: Partial<Task> = Object.fromEntries(data)
|
const task: Partial<Task> = Object.fromEntries(data)
|
||||||
|
|
||||||
await createTask(task);
|
await createTask(task)
|
||||||
await router.push('/');
|
await router.push('/')
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -18,10 +18,14 @@ const handleSubmit = async(e: Event) => {
|
|||||||
<div>
|
<div>
|
||||||
<form @submit.prevent="handleSubmit">
|
<form @submit.prevent="handleSubmit">
|
||||||
<fieldset class="fieldset">
|
<fieldset class="fieldset">
|
||||||
<legend class="fieldset-legend">What is your name?</legend>
|
<legend class="fieldset-legend">
|
||||||
<input type="text" class="input" name="title" placeholder="Type here" />
|
What is your name?
|
||||||
|
</legend>
|
||||||
|
<input type="text" class="input" name="title" placeholder="Type here">
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<button class="btn btn-primary">Submit</button>
|
<button class="btn btn-primary">
|
||||||
|
Submit
|
||||||
|
</button>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|||||||
@@ -1,49 +1,53 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed, onMounted, ref } from 'vue';
|
import type { Task } from '../types.ts'
|
||||||
import { useTasks } from '../composables/useTasks.ts';
|
import { PhCaretDown, PhCaretUp } from '@phosphor-icons/vue'
|
||||||
|
|
||||||
import { Task } from '../types.ts';
|
import { computed, onMounted, ref } from 'vue'
|
||||||
import { PhCaretDown, PhCaretUp } from '@phosphor-icons/vue';
|
import TodoItem from '../components/TodoItem.vue'
|
||||||
import TodoItem from '../components/TodoItem.vue';
|
import { useTasks } from '../composables/useTasks.ts'
|
||||||
|
|
||||||
const { tasks, fetchTasks } = useTasks();
|
const { tasks, fetchTasks } = useTasks()
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await fetchTasks();
|
await fetchTasks()
|
||||||
})
|
})
|
||||||
|
|
||||||
const visibleTasks = computed<Task[]>(() => tasks.value.filter(task => !task.archived))
|
const visibleTasks = computed<Task[]>(() => tasks.value.filter(task => !task.archived).sort((a, b) => a.id_ - b.id_))
|
||||||
|
|
||||||
const categorizedTasks = computed(() => visibleTasks.value.reduce())
|
const categorizedTasks = computed(() => visibleTasks.value.reduce((acc, task) => {
|
||||||
|
const tag = task.tag ?? '@uncategorized'
|
||||||
const collapsed = ref<string[]>([]);
|
acc[tag] = acc[tag] ?? []
|
||||||
|
acc[tag].push(task)
|
||||||
|
return acc
|
||||||
|
}, {} as Record<string, Task[]>))
|
||||||
|
|
||||||
|
const collapsed = ref<string[]>([])
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div class="flex flex-col gap-4">
|
<div class="flex flex-col gap-4">
|
||||||
<div class="m-4 rounded-box border border-neutral-100 shadow-md" v-for="(tasks, category) in categorizedTasks" :key="category">
|
<div v-for="(tasks, category) in categorizedTasks" :key="category" class="m-4 rounded-box border border-neutral-100 shadow-md">
|
||||||
<div class="m-4 flex justify-between items-center">
|
<div class="m-4 flex justify-between items-center">
|
||||||
<div class="badge badge-xl badge-primary">{{ category }}</div>
|
<div class="badge badge-xl badge-primary">
|
||||||
|
{{ category }}
|
||||||
|
</div>
|
||||||
<button
|
<button
|
||||||
@click="collapsed.includes(category) ? collapsed.splice(collapsed.indexOf(category), 1) : collapsed.push(category)"
|
|
||||||
class="btn btn-square btn-sm"
|
class="btn btn-square btn-sm"
|
||||||
|
@click="collapsed.includes(category) ? collapsed.splice(collapsed.indexOf(category), 1) : collapsed.push(category)"
|
||||||
>
|
>
|
||||||
<PhCaretDown :size="20" v-if="collapsed.includes(category)" />
|
<PhCaretDown v-if="collapsed.includes(category)" :size="20" />
|
||||||
<PhCaretUp :size="20" v-else />
|
<PhCaretUp v-else :size="20" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<Transition name="fade">
|
<Transition name="fade">
|
||||||
<ul v-if="!collapsed.includes(category)" class="list bg-base-100 rounded-box" >
|
<ul v-if="!collapsed.includes(category)" class="list bg-base-100 rounded-box">
|
||||||
<TodoItem v-for="task in tasks.sort((a, b) => a.id_ - b.id_)" :key="task.id" :task />
|
<TodoItem v-for="task in tasks.sort((a, b) => a.id_ - b.id_)" :key="task.id" :task />
|
||||||
</ul>
|
</ul>
|
||||||
</Transition>
|
</Transition>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
Reference in New Issue
Block a user