Compare commits
No commits in common. "3407250e9261123958aac2a809637d6886f35d30" and "7506c9850d5910ec06b24d3654513aa1e527f589" have entirely different histories.
3407250e92
...
7506c9850d
File diff suppressed because one or more lines are too long
|
|
@ -3,26 +3,22 @@ import React from 'react';
|
|||
import { useTranslation } from 'react-i18next';
|
||||
import { GoArrowSwitch } from 'react-icons/go';
|
||||
import { useObjectToEdit } from '../../zustand/ObjectToEditState';
|
||||
import { QUESTION_OBJECT_KEY } from '../../config/AppKey';
|
||||
|
||||
const Header = () => {
|
||||
const [t] = useTranslation();
|
||||
const { values, setFieldValue,setValues } = useFormikContext<any>();
|
||||
const {isBseQuestion,set_isBseQuestion} = useObjectToEdit()
|
||||
const {set_SavedQuestionData} = useObjectToEdit()
|
||||
|
||||
const handleChange = () => {
|
||||
set_SavedQuestionData(null)
|
||||
localStorage.removeItem(QUESTION_OBJECT_KEY)
|
||||
|
||||
if (isBseQuestion) {
|
||||
set_isBseQuestion(false)
|
||||
setValues(null)
|
||||
setFieldValue("isBase",0)
|
||||
|
||||
} else {
|
||||
|
||||
set_isBseQuestion(true)
|
||||
setValues(null)
|
||||
setFieldValue("isBase",1)
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -33,7 +29,7 @@ const Header = () => {
|
|||
</div>
|
||||
<div>
|
||||
<GoArrowSwitch onClick={handleChange} className="m-2" />
|
||||
{isBseQuestion || values?.isBase === 1 ? t("header.malty_exercise") :t("header.exercise") }
|
||||
{isBseQuestion ? t("header.malty_exercise") :t("header.exercise") }
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,32 +1,20 @@
|
|||
import { useEffect } from 'react';
|
||||
import { setLocalStorageQuestions } from '../utils/setLocalStorageQuestions';
|
||||
import { setLocalStorageBaseQuestions } from '../utils/setLocalStorageBaseQuestions';
|
||||
|
||||
const useSaveOnDisconnect = (noChange: boolean, QUESTION_OBJECT_KEY: string, SavedQuestionData: any) => {
|
||||
useEffect(() => {
|
||||
const handleBeforeUnload = (event: BeforeUnloadEvent) => {
|
||||
console.log("disconnect");
|
||||
if (noChange) {
|
||||
if(SavedQuestionData?.isBase ===1){
|
||||
setLocalStorageQuestions(QUESTION_OBJECT_KEY, SavedQuestionData);
|
||||
|
||||
}else{
|
||||
|
||||
setLocalStorageQuestions(QUESTION_OBJECT_KEY, SavedQuestionData);
|
||||
}
|
||||
const jsonData = JSON.stringify(SavedQuestionData);
|
||||
localStorage.setItem(QUESTION_OBJECT_KEY, jsonData);
|
||||
}
|
||||
};
|
||||
|
||||
const handleOffline = () => {
|
||||
console.log("disconnect");
|
||||
if (noChange) {
|
||||
if(SavedQuestionData?.isBase ===1){
|
||||
setLocalStorageQuestions(QUESTION_OBJECT_KEY, SavedQuestionData);
|
||||
|
||||
}else{
|
||||
|
||||
setLocalStorageQuestions(QUESTION_OBJECT_KEY, SavedQuestionData);
|
||||
}
|
||||
const jsonData = JSON.stringify(SavedQuestionData);
|
||||
localStorage.setItem(QUESTION_OBJECT_KEY, jsonData);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
import React, { Suspense, lazy, useEffect } from "react";
|
||||
import React, { useEffect } from "react";
|
||||
import { Modal, Spin } from "antd";
|
||||
import FormikForm from "../../Layout/Dashboard/FormikFormModel";
|
||||
import ModelBody from "./Model/Add";
|
||||
import { getInitialValues, getValidationSchema ,getInitialValuesBase, getValidationSchemaBase, processTags} from "./Model/formUtil";
|
||||
import { useAddQuestion, useAddQuestionAsync } from "../../api/Question";
|
||||
import { useAddQuestion } from "../../api/Question";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useNavigate, useParams } from "react-router-dom";
|
||||
import { ParamsEnum } from "../../enums/params";
|
||||
|
|
@ -13,33 +14,33 @@ import { Question } from "../../types/Item";
|
|||
import BaseForm from './Model/Malty/Add'
|
||||
import Form from './Model/Add'
|
||||
import { toast } from "react-toastify";
|
||||
const AcceptModal = lazy(() => import('./Model/AcceptModal'));
|
||||
|
||||
import AcceptModal from "./Model/AcceptModal";
|
||||
import { useModalState } from "../../zustand/Modal";
|
||||
import { ModalEnum } from "../../enums/Model";
|
||||
import { cleanObject } from "../../utils/cleanObject";
|
||||
import { hasItems } from "../../utils/hasItems";
|
||||
import { QUESTION_OBJECT_KEY } from "../../config/AppKey";
|
||||
import useSaveOnDisconnect from "../../Hooks/useSaveOnDisconnect";
|
||||
import { getLocalStorageQuestions } from "../../utils/setLocalStorageQuestions";
|
||||
import { getLocalStorage } from "../../utils/LocalStorage";
|
||||
|
||||
const AddPage: React.FC = () => {
|
||||
|
||||
const {isSuccess:isSuccessAsync,mutateAsync} = useAddQuestionAsync()
|
||||
const { mutate,isSuccess, isLoading} = useAddQuestion();
|
||||
const {isBseQuestion,set_Tags_search,set_object_to_edit,set_Success,SavedQuestionData} = useObjectToEdit()
|
||||
|
||||
const { mutate, isSuccess, isLoading ,mutateAsync} = useAddQuestion();
|
||||
const {object_to_edit,set_Tags_search,set_object_to_edit,set_Success,SavedQuestionData} = useObjectToEdit()
|
||||
|
||||
const {subject_id,lesson_id} = useParams<ParamsEnum>()
|
||||
const {isBseQuestion,set_isBseQuestion} = useObjectToEdit()
|
||||
const { setIsOpen } = useModalState((state) => state);
|
||||
|
||||
|
||||
|
||||
const handleSubmit = (values: any, { resetForm }: { resetForm: () => void }) => {
|
||||
const handleSubmit = (values: any, { resetForm }: { resetForm: () => void }) => {
|
||||
const DataToSend = structuredClone(values);
|
||||
console.log(DataToSend, "DataToSend");
|
||||
set_Tags_search(null);
|
||||
console.log(isBseQuestion);
|
||||
|
||||
if (isBseQuestion || DataToSend?.isBase === 1) {
|
||||
if (isBseQuestion) {
|
||||
const newBseQuestion = {
|
||||
"subject_id": subject_id,
|
||||
"content": DataToSend?.content,
|
||||
|
|
@ -50,14 +51,12 @@ const AddPage: React.FC = () => {
|
|||
|
||||
|
||||
|
||||
mutateAsync(newBseQuestion).then((data:any) => {
|
||||
mutateAsync(newBseQuestion).then((data) => {
|
||||
const newBseQuestionId = (data as any)?.data?.id;
|
||||
const Questions = DataToSend?.Questions;
|
||||
console.log(1);
|
||||
|
||||
Questions?.map((item: Question) => {
|
||||
const tags = processTags(item);
|
||||
console.log(item);
|
||||
|
||||
mutate({
|
||||
...item,
|
||||
|
|
@ -74,32 +73,27 @@ const AddPage: React.FC = () => {
|
|||
} else {
|
||||
const tags = processTags(DataToSend);
|
||||
mutate({ ...values, subject_id: subject_id, tags , "lessons_ids":[lesson_id] })
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
const navigate = useNavigate()
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
if (isSuccessAsync && ( SavedQuestionData?.Questions?.length > 0 ? isSuccess: true )) {
|
||||
if(isSuccess){
|
||||
toast.success(t("validation.the_possess_done_successful"))
|
||||
set_object_to_edit(null)
|
||||
set_Success(true)
|
||||
localStorage.removeItem(QUESTION_OBJECT_KEY)
|
||||
|
||||
}
|
||||
}, [isSuccess,isSuccessAsync])
|
||||
}, [isSuccess])
|
||||
|
||||
let cleanedQuestionOptions = cleanObject(SavedQuestionData);
|
||||
let noChange =hasItems(cleanedQuestionOptions)
|
||||
|
||||
|
||||
useSaveOnDisconnect(noChange, QUESTION_OBJECT_KEY, SavedQuestionData);
|
||||
|
||||
|
||||
const SavedData = getLocalStorageQuestions(QUESTION_OBJECT_KEY)
|
||||
|
||||
const SavedData = {} as any
|
||||
console.log(SavedData);
|
||||
const handleCancel = () => {
|
||||
if(!noChange){
|
||||
|
|
@ -114,17 +108,16 @@ const AddPage: React.FC = () => {
|
|||
|
||||
const [t] = useTranslation();
|
||||
|
||||
console.log(SavedData?.isBase === 1);
|
||||
|
||||
if(isBseQuestion || SavedData?.isBase === 1){
|
||||
|
||||
|
||||
|
||||
if(isBseQuestion){
|
||||
return (
|
||||
<div className="exercise_add">
|
||||
|
||||
<FormikForm
|
||||
handleSubmit={handleSubmit}
|
||||
initialValues={getInitialValuesBase(SavedData)}
|
||||
initialValues={getInitialValuesBase(object_to_edit)}
|
||||
validationSchema={getValidationSchemaBase}
|
||||
>
|
||||
|
||||
|
|
@ -145,10 +138,7 @@ const AddPage: React.FC = () => {
|
|||
</div>
|
||||
</main>
|
||||
</FormikForm>
|
||||
<Suspense fallback={<Spin/>}>
|
||||
<AcceptModal/>
|
||||
</Suspense>
|
||||
|
||||
<AcceptModal/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -157,7 +147,7 @@ const AddPage: React.FC = () => {
|
|||
|
||||
<FormikForm
|
||||
handleSubmit={handleSubmit}
|
||||
initialValues={getInitialValues(SavedData)}
|
||||
initialValues={getInitialValues(object_to_edit)}
|
||||
validationSchema={getValidationSchema}
|
||||
|
||||
>
|
||||
|
|
@ -180,9 +170,7 @@ const AddPage: React.FC = () => {
|
|||
</div>
|
||||
</main>
|
||||
</FormikForm>
|
||||
<Suspense fallback={<Spin/>}>
|
||||
<AcceptModal/>
|
||||
</Suspense>
|
||||
<AcceptModal/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ const Form = () => {
|
|||
const formik = useFormikContext<any>();
|
||||
const { isOpen } = useModalState((state) => state);
|
||||
// const {data} = useGetAllQuestion();
|
||||
const{set_Success,Success,set_SavedQuestionData} = useObjectToEdit()
|
||||
const{set_Success,Success} = useObjectToEdit()
|
||||
|
||||
useEffect(() => {
|
||||
if (Success) {
|
||||
|
|
@ -29,10 +29,6 @@ const Form = () => {
|
|||
}
|
||||
}, [Success]);
|
||||
|
||||
useEffect(() => {
|
||||
set_SavedQuestionData(formik.values)
|
||||
}, [formik?.values])
|
||||
|
||||
// console.log(formik?.errors);
|
||||
console.log(formik?.values?.Questions,"formik?.values?.Questions");
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
import * as Yup from "yup";
|
||||
import { Question } from "../../../types/Item";
|
||||
import { getLocalStorage } from "../../../utils/LocalStorage";
|
||||
import { QUESTION_OBJECT_KEY } from "../../../config/AppKey";
|
||||
|
||||
|
||||
export const getInitialValues = (objectToEdit: Question): any => {
|
||||
|
|
@ -9,14 +7,12 @@ export const getInitialValues = (objectToEdit: Question): any => {
|
|||
return { ...item, key: index }
|
||||
});
|
||||
|
||||
|
||||
|
||||
return {
|
||||
id: objectToEdit?.id ?? null,
|
||||
content: objectToEdit?.content ?? "",
|
||||
image: objectToEdit?.image ?? "",
|
||||
subject_id: objectToEdit?.subject_id ?? '',
|
||||
isBase: 0,
|
||||
isBase: objectToEdit?.isBase,
|
||||
parent_id: objectToEdit?.parent_id ?? '',
|
||||
QuestionOptions: objectToEdit?.QuestionOptions ?? [],
|
||||
tags: tags ?? [],
|
||||
|
|
@ -67,7 +63,7 @@ export const getInitialValuesBase = (objectToEdit: Question): any => {
|
|||
content: objectToEdit?.content ?? "",
|
||||
image: objectToEdit?.image ?? "",
|
||||
subject_id: objectToEdit?.subject_id ?? '',
|
||||
isBase: 1,
|
||||
isBase: objectToEdit?.isBase,
|
||||
parent_id: objectToEdit?.parent_id ?? '',
|
||||
Questions: questions,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -6,8 +6,8 @@ import { useParams } from "react-router-dom";
|
|||
import { ParamsEnum } from "../../enums/params";
|
||||
|
||||
const App: React.FC = () => {
|
||||
const {lesson_id} = useParams<ParamsEnum>()
|
||||
const response = useGetAllQuestion({ lesson_id:lesson_id, pagination: true });
|
||||
const {subject_id} = useParams<ParamsEnum>()
|
||||
const response = useGetAllQuestion({ subject_id:subject_id, pagination: true });
|
||||
|
||||
return <DataTable response={response} useColumns={useColumns} />;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -11,13 +11,10 @@ const API = {
|
|||
};
|
||||
|
||||
const KEY = "question";
|
||||
const KEY2 = "questionBases";
|
||||
|
||||
export const useGetAllQuestion = (params?: any) =>
|
||||
useGetQuery(KEY, API.GET, params);
|
||||
export const useAddQuestion = () => useAddMutation(KEY, API.ADD,false);
|
||||
export const useAddQuestionAsync = () => useAddMutation(KEY2, API.ADD);
|
||||
|
||||
export const useUpdateQuestion = (params?: any) =>
|
||||
useUpdateMutation(KEY, API.GET,false);
|
||||
export const useDeleteQuestion = (params?: any) =>
|
||||
|
|
|
|||
|
|
@ -7,24 +7,19 @@ import { AxiosResponse } from "../../types/Axios";
|
|||
function useAddMutation(
|
||||
key: string,
|
||||
url: string,
|
||||
toast: boolean = true
|
||||
toast:boolean = true
|
||||
|
||||
): UseMutationResult<AxiosResponse, unknown, any, unknown> {
|
||||
const axios = useAxios();
|
||||
console.log(toast,key);
|
||||
|
||||
|
||||
return useMutation<AxiosResponse, unknown, any, unknown>(
|
||||
|
||||
async (dataToSend) => {
|
||||
const filterDataToSend = filterData(dataToSend);
|
||||
|
||||
const { data } = await axios.post(url, filterDataToSend, {
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data",
|
||||
["X-Custom-Message"] : toast ,
|
||||
[HEADER_KEY]: key,
|
||||
|
||||
["X-Custom-Message"] : toast
|
||||
},
|
||||
});
|
||||
return data;
|
||||
|
|
|
|||
|
|
@ -43,7 +43,6 @@ function useAxios() {
|
|||
|
||||
const key = response.config.headers[HEADER_KEY];
|
||||
const isToasted = response.config.headers["X-Custom-Message"];
|
||||
console.log(isToasted);
|
||||
|
||||
const ResponseMessage =
|
||||
responseMsg || t("validation.the_possess_done_successful");
|
||||
|
|
|
|||
|
|
@ -16,29 +16,3 @@ export const setLocalStorage = (key: string, data: any) => {
|
|||
console.error("Error stringify data for localStorage", error);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export const setLocalStorageWithFile = async (key: string, data: any) => {
|
||||
try {
|
||||
if (data.image instanceof File) {
|
||||
const base64String = await convertFileToBase64(data.image);
|
||||
data.image = base64String;
|
||||
}
|
||||
const jsonData = JSON.stringify(data);
|
||||
localStorage.setItem(key, jsonData);
|
||||
} catch (error) {
|
||||
console.error("Error stringifying data for localStorage", error);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
export const convertFileToBase64 = (file: File): Promise<string> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const reader = new FileReader();
|
||||
reader.onloadend = () => {
|
||||
resolve(reader.result as string);
|
||||
};
|
||||
reader.onerror = reject;
|
||||
reader.readAsDataURL(file);
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,15 +0,0 @@
|
|||
export const base64StringToFile = (base64String:string) => {
|
||||
// Convert base64 to blob
|
||||
const byteCharacters = atob(base64String.split(',')[1]);
|
||||
const byteNumbers = new Array(byteCharacters.length);
|
||||
for (let i = 0; i < byteCharacters.length; i++) {
|
||||
byteNumbers[i] = byteCharacters.charCodeAt(i);
|
||||
}
|
||||
const byteArray = new Uint8Array(byteNumbers);
|
||||
const blob = new Blob([byteArray], { type: 'image/jpeg' });
|
||||
|
||||
// Create a File object from Blob (optional: provide a filename)
|
||||
const file = new File([blob], 'image.jpg', { type: 'image/jpeg' });
|
||||
|
||||
return file;
|
||||
};
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
import { convertFileToBase64 } from "./LocalStorage";
|
||||
import { base64StringToFile } from "./base64StringToFile";
|
||||
|
||||
export const setLocalStorageBaseQuestions = async (key: string, data: any) => {
|
||||
try {
|
||||
// Convert the main image if it is a File
|
||||
if (data.image instanceof File) {
|
||||
data.image = await convertFileToBase64(data.image);
|
||||
}
|
||||
|
||||
if (Array.isArray(data.Questions)) {
|
||||
for (const option of data.Questions) {
|
||||
if (option.image instanceof File) {
|
||||
option.image = await convertFileToBase64(option.image);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Array.isArray(data.Questions.QuestionOptions)) {
|
||||
for (const option of data.Questions.QuestionOptions) {
|
||||
if (option.answer_image instanceof File) {
|
||||
option.answer_image = await convertFileToBase64(option.answer_image);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const jsonData = JSON.stringify(data);
|
||||
localStorage.setItem(key, jsonData);
|
||||
} catch (error) {
|
||||
console.error("Error stringifying data for localStorage", error);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
export const getLocalStorageBaseQuestions = (key: string): any | null => {
|
||||
try {
|
||||
const jsonData = localStorage.getItem(key);
|
||||
if (!jsonData) return null;
|
||||
|
||||
const data = JSON.parse(jsonData);
|
||||
|
||||
// Convert the main image from base64 to File if necessary
|
||||
if (typeof data.image === 'string' && data.image.length > 0) {
|
||||
data.image = base64StringToFile(data.image);
|
||||
}
|
||||
|
||||
// Convert each image in Questions from base64 to File if necessary
|
||||
if (Array.isArray(data.Questions)) {
|
||||
for (const option of data.Questions) {
|
||||
if (typeof option.image === 'string' ) {
|
||||
option.image = base64StringToFile(option.image);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Array.isArray(data.Questions.QuestionOptions)) {
|
||||
for (const option of data.Questions.QuestionOptions) {
|
||||
if (typeof option.answer_image === 'string' ) {
|
||||
option.answer_image = base64StringToFile(option.answer_image);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error("Error parsing data from localStorage", error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
import { convertFileToBase64 } from "./LocalStorage";
|
||||
import { base64StringToFile } from "./base64StringToFile";
|
||||
|
||||
export const setLocalStorageQuestions = async (key: string, data: any) => {
|
||||
try {
|
||||
// Convert the main image if it is a File
|
||||
if (data.image instanceof File) {
|
||||
data.image = await convertFileToBase64(data.image);
|
||||
}
|
||||
|
||||
// Check for QuestionOptions array and convert answer_image if it's a File
|
||||
if (Array.isArray(data.QuestionOptions)) {
|
||||
for (const option of data.QuestionOptions) {
|
||||
if (option.answer_image instanceof File) {
|
||||
option.answer_image = await convertFileToBase64(option.answer_image);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for Questions array and convert image if it's a File
|
||||
if (Array.isArray(data.Questions)) {
|
||||
for (const question of data.Questions) {
|
||||
if (question.image instanceof File) {
|
||||
question.image = await convertFileToBase64(question.image);
|
||||
}
|
||||
// Check for nested QuestionOptions and convert answer_image if it's a File
|
||||
if (Array.isArray(question.QuestionOptions)) {
|
||||
for (const option of question.QuestionOptions) {
|
||||
if (option.answer_image instanceof File) {
|
||||
option.answer_image = await convertFileToBase64(option.answer_image);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const jsonData = JSON.stringify(data);
|
||||
localStorage.setItem(key, jsonData);
|
||||
} catch (error) {
|
||||
console.error("Error stringifying data for localStorage", error);
|
||||
}
|
||||
};
|
||||
|
||||
export const getLocalStorageQuestions = (key: string): any | null => {
|
||||
try {
|
||||
const jsonData = localStorage.getItem(key);
|
||||
if (!jsonData) return null;
|
||||
|
||||
const data = JSON.parse(jsonData);
|
||||
|
||||
// Convert back the main image if it's a base64 string
|
||||
if (typeof data.image === 'string' && data.image.length > 0) {
|
||||
data.image = base64StringToFile(data.image);
|
||||
}
|
||||
|
||||
// Check for QuestionOptions array and convert answer_image if it's a base64 string
|
||||
if (Array.isArray(data.QuestionOptions)) {
|
||||
for (const option of data.QuestionOptions) {
|
||||
if (typeof option.answer_image === 'string' && option.answer_image.length > 0) {
|
||||
option.answer_image = base64StringToFile(option.answer_image);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check for Questions array and convert image if it's a base64 string
|
||||
if (Array.isArray(data.Questions)) {
|
||||
for (const question of data.Questions) {
|
||||
if (typeof question.image === 'string' && question.image.length > 0) {
|
||||
question.image = base64StringToFile(question.image);
|
||||
}
|
||||
// Check for nested QuestionOptions and convert answer_image if it's a base64 string
|
||||
if (Array.isArray(question.QuestionOptions)) {
|
||||
for (const option of question.QuestionOptions) {
|
||||
if (typeof option.answer_image === 'string' && option.answer_image.length > 0) {
|
||||
option.answer_image = base64StringToFile(option.answer_image);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return data;
|
||||
} catch (error) {
|
||||
console.error("Error parsing data from localStorage", error);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user