Merge pull request #323 from pateljannat/start-learning-fixes
This commit is contained in:
@@ -33,6 +33,18 @@ class CourseEvaluator(Document):
|
|||||||
frappe.throw(_("Slot Times are overlapping for some schedules."))
|
frappe.throw(_("Slot Times are overlapping for some schedules."))
|
||||||
|
|
||||||
@frappe.whitelist()
|
@frappe.whitelist()
|
||||||
def get_schedule(course):
|
def get_schedule(course, date):
|
||||||
evaluator = frappe.db.get_value("LMS Course", course, "evaluator")
|
evaluator = frappe.db.get_value("LMS Course", course, "evaluator")
|
||||||
return frappe.get_all("Evaluator Schedule", filters={"parent": evaluator}, fields=["day", "start_time", "end_time"])
|
all_slots = frappe.get_all("Evaluator Schedule",
|
||||||
|
filters = { "parent": evaluator },
|
||||||
|
fields = ["day", "start_time", "end_time"])
|
||||||
|
booked_slots = frappe.get_all("LMS Certificate Request",
|
||||||
|
filters = {"course": course, "date": date},
|
||||||
|
fields = ["start_time"])
|
||||||
|
|
||||||
|
for slot in booked_slots:
|
||||||
|
same_slot = list(filter(lambda x: x.start_time == slot.start_time, all_slots))
|
||||||
|
if len(same_slot):
|
||||||
|
all_slots.remove(same_slot[0])
|
||||||
|
|
||||||
|
return all_slots
|
||||||
|
|||||||
@@ -191,6 +191,7 @@
|
|||||||
"fieldname": "evaluator",
|
"fieldname": "evaluator",
|
||||||
"fieldtype": "Link",
|
"fieldtype": "Link",
|
||||||
"label": "Evaluator",
|
"label": "Evaluator",
|
||||||
|
"mandatory_depends_on": "eval: doc.grant_certificate_after == \"Evaluation\"",
|
||||||
"options": "Course Evaluator"
|
"options": "Course Evaluator"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -236,7 +237,7 @@
|
|||||||
"link_fieldname": "course"
|
"link_fieldname": "course"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"modified": "2022-04-07 12:27:05.353788",
|
"modified": "2022-04-08 14:36:22.254656",
|
||||||
"modified_by": "Administrator",
|
"modified_by": "Administrator",
|
||||||
"module": "LMS",
|
"module": "LMS",
|
||||||
"name": "LMS Course",
|
"name": "LMS Course",
|
||||||
|
|||||||
@@ -1410,3 +1410,7 @@ pre {
|
|||||||
.course-content-parent .course-details-outline .course-home-headings {
|
.course-content-parent .course-details-outline .course-home-headings {
|
||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-outline-primary {
|
||||||
|
border: 1px solid var(--primary-color);
|
||||||
|
}
|
||||||
|
|||||||
@@ -34,11 +34,11 @@
|
|||||||
|
|
||||||
{% macro BreadCrumb(course, lesson) %}
|
{% macro BreadCrumb(course, lesson) %}
|
||||||
<div class="breadcrumb">
|
<div class="breadcrumb">
|
||||||
<a class="dark-links" href="/courses">{{ _("All Courses") }}</a>
|
<a class="dark-links" href="/courses">{{ _("All Courses") }}</a>
|
||||||
<img class="ml-1 mr-1" src="/assets/lms/icons/chevron-right.svg">
|
<img class="ml-1 mr-1" src="/assets/lms/icons/chevron-right.svg">
|
||||||
<a class="dark-links" href="/courses/{{ course.name }}">{{ course.title }}</a>
|
<a class="dark-links" href="/courses/{{ course.name }}">{{ course.title }}</a>
|
||||||
<img class="ml-1 mr-1" src="/assets/lms/icons/chevron-right.svg">
|
<img class="ml-1 mr-1" src="/assets/lms/icons/chevron-right.svg">
|
||||||
<span class="breadcrumb-destination">{{ lesson.title }}</span>
|
<span class="breadcrumb-destination">{{ lesson.title }}</span>
|
||||||
</div>
|
</div>
|
||||||
{% endmacro %}
|
{% endmacro %}
|
||||||
|
|
||||||
@@ -94,7 +94,7 @@
|
|||||||
{{ render_html(lesson.body) }}
|
{{ render_html(lesson.body) }}
|
||||||
{% else %}
|
{% else %}
|
||||||
<div class="">
|
<div class="">
|
||||||
<a class="button is-primary pull-right" href="/courses/{{ course.name }}"> {{ _("Start Learning") }} </a>
|
<div class="button is-primary pull-right join-batch" data-course="{{ course.name | urlencode }}"> {{ _("Start Learning") }} </div>
|
||||||
<div class=""> {{ _("This lesson is not available for preview. Please join the course to access it.") }} </div>
|
<div class=""> {{ _("This lesson is not available for preview. Please join the course to access it.") }} </div>
|
||||||
</div>
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
@@ -1,49 +1,53 @@
|
|||||||
frappe.ready(() => {
|
frappe.ready(() => {
|
||||||
|
|
||||||
localStorage.removeItem($("#quiz-title").text());
|
localStorage.removeItem($("#quiz-title").text());
|
||||||
fetch_assignments();
|
fetch_assignments();
|
||||||
|
|
||||||
save_current_lesson();
|
save_current_lesson();
|
||||||
|
|
||||||
$(".option").click((e) => {
|
$(".option").click((e) => {
|
||||||
enable_check(e);
|
enable_check(e);
|
||||||
})
|
})
|
||||||
|
|
||||||
$(".mark-progress").click((e) => {
|
$(".mark-progress").click((e) => {
|
||||||
mark_progress(e);
|
mark_progress(e);
|
||||||
});
|
});
|
||||||
|
|
||||||
$(".next").click((e) => {
|
$(".next").click((e) => {
|
||||||
mark_progress(e);
|
mark_progress(e);
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#summary").click((e) => {
|
$("#summary").click((e) => {
|
||||||
quiz_summary(e);
|
quiz_summary(e);
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#check").click((e) => {
|
$("#check").click((e) => {
|
||||||
check_answer(e);
|
check_answer(e);
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#next").click((e) => {
|
$("#next").click((e) => {
|
||||||
mark_active_question(e);
|
mark_active_question(e);
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#try-again").click((e) => {
|
$("#try-again").click((e) => {
|
||||||
try_quiz_again(e);
|
try_quiz_again(e);
|
||||||
});
|
});
|
||||||
|
|
||||||
$("#certification").click((e) => {
|
$("#certification").click((e) => {
|
||||||
create_certificate(e);
|
create_certificate(e);
|
||||||
});
|
});
|
||||||
|
|
||||||
$(".submit-work").click((e) => {
|
$(".submit-work").click((e) => {
|
||||||
attach_work(e);
|
attach_work(e);
|
||||||
});
|
});
|
||||||
|
|
||||||
$(".clear-work").click((e) => {
|
$(".clear-work").click((e) => {
|
||||||
clear_work(e);
|
clear_work(e);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$(".join-batch").click((e) => {
|
||||||
|
join_course(e)
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -240,6 +244,36 @@ const add_to_local_storage = (quiz_name, current_index, answer, is_correct) => {
|
|||||||
localStorage.setItem(quiz_name, JSON.stringify(quiz_stored))
|
localStorage.setItem(quiz_name, JSON.stringify(quiz_stored))
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const join_course = (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
let course = $(e.currentTarget).attr("data-course")
|
||||||
|
if (frappe.session.user == "Guest") {
|
||||||
|
window.location.href = `/login?redirect-to=/courses/${course}`;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let batch = $(e.currentTarget).attr("data-batch");
|
||||||
|
batch = batch ? decodeURIComponent(batch) : "";
|
||||||
|
frappe.call({
|
||||||
|
"method": "lms.lms.doctype.lms_batch_membership.lms_batch_membership.create_membership",
|
||||||
|
"args": {
|
||||||
|
"batch": batch ? batch : "",
|
||||||
|
"course": course
|
||||||
|
},
|
||||||
|
"callback": (data) => {
|
||||||
|
if (data.message == "OK") {
|
||||||
|
frappe.msgprint({
|
||||||
|
"title": __("Successfully Enrolled"),
|
||||||
|
"message": __("You are now a student of this course.")
|
||||||
|
});
|
||||||
|
setTimeout(function () {
|
||||||
|
window.location.href = `/courses/${course}/learn/1.1`;
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const create_certificate = (e) => {
|
const create_certificate = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
course = $(".title").attr("data-course");
|
course = $(".title").attr("data-course");
|
||||||
|
|||||||
@@ -105,7 +105,7 @@
|
|||||||
{{ _("You have opted to be notified for this course. You will receive an email when the course becomes available.") }}
|
{{ _("You have opted to be notified for this course. You will receive an email when the course becomes available.") }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{% if certificate_request %}
|
{% if certificate_request and not certificate %}
|
||||||
<p class="mb-2"> <b>{{ _("Evaluation On: ") }}</b>
|
<p class="mb-2"> <b>{{ _("Evaluation On: ") }}</b>
|
||||||
{{ _("{0} at {1}").format(frappe.utils.format_date(certificate_request.date, "medium"),
|
{{ _("{0} at {1}").format(frappe.utils.format_date(certificate_request.date, "medium"),
|
||||||
frappe.utils.format_time(certificate_request.start_time, "short")) }} </p>
|
frappe.utils.format_time(certificate_request.start_time, "short")) }} </p>
|
||||||
@@ -199,7 +199,6 @@
|
|||||||
</a>
|
</a>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% set certificate = is_certified(course.name) %}
|
|
||||||
{% set progress = frappe.utils.cint(membership.progress) %}
|
{% set progress = frappe.utils.cint(membership.progress) %}
|
||||||
{% if membership and course.enable_certification %}
|
{% if membership and course.enable_certification %}
|
||||||
{% if certificate %}
|
{% if certificate %}
|
||||||
@@ -225,7 +224,7 @@
|
|||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="modal-header">
|
<div class="modal-header">
|
||||||
<div class="font-weight-bold">{{ _("Pick a Slot") }}</div>
|
<div class="font-weight-bold">{{ _("Pick a Slot") }}</div>
|
||||||
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
|
<button type="button" class="close close-slot-modal" data-dismiss="modal" aria-label="Close">
|
||||||
<span aria-hidden="true">×</span>
|
<span aria-hidden="true">×</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -238,8 +237,8 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="control-input-wrapper">
|
<div class="control-input-wrapper">
|
||||||
<div class="control-input">
|
<div class="control-input">
|
||||||
<input type="date" class="input-with-feedback form-control bold" data-fieldtype="Date"
|
<input type="date" class="input-with-feedback form-control bold" data-fieldtype="Date" data-course="{{ course.name | urlencode }}"
|
||||||
id="slot-date" min="{{ frappe.utils.format_date(frappe.utils.getdate(), 'yyyy-mm-dd') }}">
|
id="slot-date" min="{{ frappe.utils.format_date(frappe.utils.add_days(frappe.utils.getdate(), 1), 'yyyy-mm-dd') }}">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -256,6 +255,10 @@
|
|||||||
<p id="no-slots-message" class="small text-danger hide"> {{ _("There are no slots available on this day.") }} </p>
|
<p id="no-slots-message" class="small text-danger hide"> {{ _("There are no slots available on this day.") }} </p>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<div class="button is-primary" data-course="{{ course.name | urlencode}}" id="submit-slot">
|
||||||
|
{{ _("Submit") }}</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ frappe.ready(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
$(".join-batch").click((e) => {
|
$(".join-batch").click((e) => {
|
||||||
join_course(e)
|
join_course(e);
|
||||||
});
|
});
|
||||||
|
|
||||||
$(".view-all-mentors").click((e) => {
|
$(".view-all-mentors").click((e) => {
|
||||||
@@ -46,10 +46,18 @@ frappe.ready(() => {
|
|||||||
display_slots(e);
|
display_slots(e);
|
||||||
});
|
});
|
||||||
|
|
||||||
$(document).on("click", ".slot", (e) => {
|
$("#submit-slot").click((e) => {
|
||||||
submit_slot(e);
|
submit_slot(e);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$(".close-slot-modal").click((e) => {
|
||||||
|
close_slot_modal(e);
|
||||||
|
});
|
||||||
|
|
||||||
|
$(document).on("click", ".slot", (e) => {
|
||||||
|
select_slot(e);
|
||||||
|
});
|
||||||
|
|
||||||
$(document).scroll(function() {
|
$(document).scroll(function() {
|
||||||
let timer;
|
let timer;
|
||||||
clearTimeout(timer);
|
clearTimeout(timer);
|
||||||
@@ -93,31 +101,35 @@ var cancel_mentor_request = (e) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
var join_course = (e) => {
|
const join_course = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
var course = $(e.currentTarget).attr("data-course")
|
let course = $(e.currentTarget).attr("data-course");
|
||||||
if (frappe.session.user == "Guest") {
|
if (frappe.session.user == "Guest") {
|
||||||
window.location.href = `/login?redirect-to=/courses/${course}`;
|
window.location.href = `/login?redirect-to=/courses/${course}`;
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
var batch = $(e.currentTarget).attr("data-batch");
|
|
||||||
batch = batch ? decodeURIComponent(batch) : "";
|
|
||||||
frappe.call({
|
|
||||||
"method": "lms.lms.doctype.lms_batch_membership.lms_batch_membership.create_membership",
|
|
||||||
"args": {
|
|
||||||
"batch": batch ? batch : "",
|
|
||||||
"course": course
|
|
||||||
},
|
|
||||||
"callback": (data) => {
|
|
||||||
if (data.message == "OK") {
|
|
||||||
frappe.msgprint(__("You are now a student of this course."));
|
|
||||||
setTimeout(function () {
|
|
||||||
window.location.href = `/courses/${course}/learn/1.1`;
|
|
||||||
}, 2000);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
let batch = $(e.currentTarget).attr("data-batch");
|
||||||
|
batch = batch ? decodeURIComponent(batch) : "";
|
||||||
|
frappe.call({
|
||||||
|
"method": "lms.lms.doctype.lms_batch_membership.lms_batch_membership.create_membership",
|
||||||
|
"args": {
|
||||||
|
"batch": batch ? batch : "",
|
||||||
|
"course": course
|
||||||
|
},
|
||||||
|
"callback": (data) => {
|
||||||
|
if (data.message == "OK") {
|
||||||
|
frappe.msgprint({
|
||||||
|
"title": __("Successfully Enrolled"),
|
||||||
|
"message": __("You are now a student of this course.")
|
||||||
|
});
|
||||||
|
setTimeout(function () {
|
||||||
|
window.location.href = `/courses/${course}/learn/1.1`;
|
||||||
|
}, 2000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
};
|
||||||
|
|
||||||
var view_all_mentors = (e) => {
|
var view_all_mentors = (e) => {
|
||||||
$(".wrapped").each((i, element) => {
|
$(".wrapped").each((i, element) => {
|
||||||
@@ -251,29 +263,14 @@ const submit_for_review = (e) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const apply_cetificate = (e) => {
|
const apply_cetificate = (e) => {
|
||||||
frappe.call({
|
$("#slot-modal").modal("show");
|
||||||
method: "lms.lms.doctype.course_evaluator.course_evaluator.get_schedule",
|
|
||||||
args: {
|
|
||||||
"course": $(e.currentTarget).data("course")
|
|
||||||
},
|
|
||||||
callback: (data) => {
|
|
||||||
let options = "";
|
|
||||||
data.message.forEach((obj) => {
|
|
||||||
options += `<button type="button" class="btn btn-sm btn-secondary mr-3 slot hide"
|
|
||||||
data-course="${$(e.currentTarget).data("course")}"
|
|
||||||
data-day="${obj.day}" data-start="${obj.start_time}" data-end="${obj.end_time}">
|
|
||||||
${obj.day} ${obj.start_time} - ${obj.end_time}</button>`;
|
|
||||||
});
|
|
||||||
e.preventDefault();
|
|
||||||
$("#slot-modal .slots").html(options);
|
|
||||||
$("#slot-modal").modal("show");
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const submit_slot = (e) => {
|
const submit_slot = (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
const slot = $(e.currentTarget);
|
const slot = window.selected_slot;
|
||||||
frappe.call({
|
frappe.call({
|
||||||
method: "lms.lms.doctype.lms_certificate_request.lms_certificate_request.create_certificate_request",
|
method: "lms.lms.doctype.lms_certificate_request.lms_certificate_request.create_certificate_request",
|
||||||
args: {
|
args: {
|
||||||
@@ -294,17 +291,51 @@ const submit_slot = (e) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const display_slots = (e) => {
|
const display_slots = (e) => {
|
||||||
const weekday = ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];
|
frappe.call({
|
||||||
const day = weekday[new Date($(e.currentTarget).val()).getDay()]
|
method: "lms.lms.doctype.course_evaluator.course_evaluator.get_schedule",
|
||||||
|
args: {
|
||||||
|
"course": $(e.currentTarget).data("course"),
|
||||||
|
"date": $(e.currentTarget).val()
|
||||||
|
},
|
||||||
|
callback: (data) => {
|
||||||
|
let options = "";
|
||||||
|
data.message.forEach((obj) => {
|
||||||
|
options += `<button type="button" class="btn btn-sm btn-secondary mr-3 slot hide"
|
||||||
|
data-course="${$(e.currentTarget).data("course")}"
|
||||||
|
data-day="${obj.day}" data-start="${obj.start_time}" data-end="${obj.end_time}">
|
||||||
|
${format_time(obj.start_time)} - ${format_time(obj.end_time)}</button>`;
|
||||||
|
});
|
||||||
|
e.preventDefault();
|
||||||
|
$("#slot-modal .slots").html(options);
|
||||||
|
const weekday = ["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"];
|
||||||
|
const day = weekday[new Date($(e.currentTarget).val()).getDay()]
|
||||||
|
|
||||||
$(".slot").addClass("hide");
|
$(".slot").addClass("hide");
|
||||||
$(".slot-label").addClass("hide");
|
$(".slot-label").addClass("hide");
|
||||||
|
|
||||||
if ($(`[data-day='${day}']`).length) {
|
if ($(`[data-day='${day}']`).length) {
|
||||||
$(".slot-label").removeClass("hide");
|
$(".slot-label").removeClass("hide");
|
||||||
$(`[data-day='${day}']`).removeClass("hide");
|
$(`[data-day='${day}']`).removeClass("hide");
|
||||||
$("#no-slots-message").addClass("hide");
|
$("#no-slots-message").addClass("hide");
|
||||||
} else {
|
} else {
|
||||||
$("#no-slots-message").removeClass("hide");
|
$("#no-slots-message").removeClass("hide");
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const select_slot = (e) => {
|
||||||
|
$(".slot").removeClass("btn-outline-primary");
|
||||||
|
$(e.currentTarget).addClass("btn-outline-primary");
|
||||||
|
window.selected_slot = $(e.currentTarget);
|
||||||
|
};
|
||||||
|
|
||||||
|
const format_time = (time) => {
|
||||||
|
let date = moment(new Date()).format("ddd MMM DD YYYY");
|
||||||
|
return moment(`${date} ${time}`).format("HH:mm a");
|
||||||
|
};
|
||||||
|
|
||||||
|
const close_slot_modal = (e) => {
|
||||||
|
$("#slot-date").val("");
|
||||||
|
$(".slot-label").addClass("hide");
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import frappe
|
import frappe
|
||||||
from lms.lms.doctype.lms_settings.lms_settings import check_profile_restriction
|
from lms.lms.doctype.lms_settings.lms_settings import check_profile_restriction
|
||||||
from lms.lms.utils import get_membership, is_instructor
|
from lms.lms.utils import get_membership, is_instructor, is_certified
|
||||||
|
|
||||||
def get_context(context):
|
def get_context(context):
|
||||||
context.no_cache = 1
|
context.no_cache = 1
|
||||||
@@ -32,6 +32,7 @@ def get_context(context):
|
|||||||
context.membership = membership
|
context.membership = membership
|
||||||
context.restriction = check_profile_restriction()
|
context.restriction = check_profile_restriction()
|
||||||
context.show_start_learing_cta = show_start_learing_cta(course, membership, context.restriction)
|
context.show_start_learing_cta = show_start_learing_cta(course, membership, context.restriction)
|
||||||
|
context.certificate = is_certified(course.name)
|
||||||
context.certificate_request = frappe.db.get_value("LMS Certificate Request",
|
context.certificate_request = frappe.db.get_value("LMS Certificate Request",
|
||||||
{
|
{
|
||||||
"course": course.name,
|
"course": course.name,
|
||||||
|
|||||||
@@ -14,9 +14,9 @@ def get_context(context):
|
|||||||
|
|
||||||
def get_courses():
|
def get_courses():
|
||||||
courses = frappe.get_all("LMS Course",
|
courses = frappe.get_all("LMS Course",
|
||||||
filters={"published": True},
|
filters={"published": True},
|
||||||
fields=["name", "upcoming", "title", "image", "enable_certification",
|
fields=["name", "upcoming", "title", "image", "enable_certification",
|
||||||
"paid_certificate", "price_certificate", "currency"])
|
"paid_certificate", "price_certificate", "currency"])
|
||||||
|
|
||||||
live_courses, upcoming_courses = [], []
|
live_courses, upcoming_courses = [], []
|
||||||
for course in courses:
|
for course in courses:
|
||||||
|
|||||||
Reference in New Issue
Block a user