feat(course): auto-enrol button for paid course join requests (#769)
* feat(course): add auto-enrol flow for paid course join requests - Extend teacher buy-request email with manual access steps and an Auto Enrol this Student button - Link teachers to the course People page with grantAccess query param - Add grant-access modal that sends the same org audience invite used by the Invite new students flow Co-authored-by: Rotimi Best <[email protected]> * fix(i18n): translate grant access modal strings Replace English placeholders copied into non-en locale files with proper translations for each supported language. Co-authored-by: Rotimi Best <[email protected]> * fix(email): shorten auto-enrol button label per review Co-authored-by: Rotimi Best <[email protected]> * fix(email): use American spelling Enroll in auto-enroll CTA Co-authored-by: Rotimi Best <[email protected]> * fix(i18n): localize Danish grant-access send invite label Co-authored-by: Rotimi Best <[email protected]> --------- Co-authored-by: Rotimi Best <[email protected]>
This commit is contained in:
co-authored by
Rotimi Best
parent
634ffeab0f
commit
6c3a982daf
@@ -5,6 +5,7 @@ import { getCourseWithOrgData } from '@cio/db/queries/course';
|
|||||||
import { buildEmailFromName, buildEmailBranding } from '@cio/email';
|
import { buildEmailFromName, buildEmailBranding } from '@cio/email';
|
||||||
import { enqueueTransactionalEmail } from '@api/services/jobs';
|
import { enqueueTransactionalEmail } from '@api/services/jobs';
|
||||||
import { trackServerEvent, SERVER_EVENTS } from '@cio/analytics';
|
import { trackServerEvent, SERVER_EVENTS } from '@cio/analytics';
|
||||||
|
import { getDashboardBaseUrl } from '@cio/core/config/dashboard-url';
|
||||||
|
|
||||||
export interface PaymentRequestData {
|
export interface PaymentRequestData {
|
||||||
courseId: string;
|
courseId: string;
|
||||||
@@ -47,6 +48,15 @@ export async function createPaymentRequest(data: PaymentRequestData) {
|
|||||||
|
|
||||||
const teacherEmail = teacherResult[0].email;
|
const teacherEmail = teacherResult[0].email;
|
||||||
|
|
||||||
|
const dashboardOrg = {
|
||||||
|
siteName: course.orgSiteName,
|
||||||
|
customDomain: course.orgCustomDomain,
|
||||||
|
isCustomDomainVerified: course.orgIsCustomDomainVerified
|
||||||
|
};
|
||||||
|
const dashboardBaseUrl = getDashboardBaseUrl(dashboardOrg);
|
||||||
|
const courseUrl = `${dashboardBaseUrl}/courses/${data.courseId}`;
|
||||||
|
const autoEnrollUrl = `${dashboardBaseUrl}/courses/${data.courseId}/people?grantAccess=${encodeURIComponent(data.studentEmail)}`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await enqueueTransactionalEmail('teacherStudentBuyRequest', {
|
await enqueueTransactionalEmail('teacherStudentBuyRequest', {
|
||||||
to: teacherEmail,
|
to: teacherEmail,
|
||||||
@@ -54,6 +64,8 @@ export async function createPaymentRequest(data: PaymentRequestData) {
|
|||||||
courseName,
|
courseName,
|
||||||
studentEmail: data.studentEmail,
|
studentEmail: data.studentEmail,
|
||||||
studentFullname: data.studentFullname,
|
studentFullname: data.studentFullname,
|
||||||
|
courseUrl,
|
||||||
|
autoEnrollUrl,
|
||||||
branding
|
branding
|
||||||
},
|
},
|
||||||
from: buildEmailFromName('ClassroomIO'),
|
from: buildEmailFromName('ClassroomIO'),
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import { page } from '$app/state';
|
||||||
|
import { goto } from '$app/navigation';
|
||||||
|
import { resolve } from '$app/paths';
|
||||||
|
import * as Dialog from '@cio/ui/base/dialog';
|
||||||
|
import { Button } from '@cio/ui/base/button';
|
||||||
|
import { t } from '$lib/utils/functions/translations';
|
||||||
|
import { courseApi } from '$features/course/api';
|
||||||
|
import { orgApi } from '$features/org/api/org.svelte';
|
||||||
|
import { profile } from '$lib/utils/store/user';
|
||||||
|
import { isStudentLimitReached } from '$lib/utils/store/org';
|
||||||
|
import { UpgradeBanner } from '$features/ui';
|
||||||
|
|
||||||
|
const GRANT_ACCESS_PARAM = 'grantAccess';
|
||||||
|
|
||||||
|
let courseId = $derived(courseApi.course?.id ?? '');
|
||||||
|
const studentEmail = $derived(new URLSearchParams(page.url.search).get(GRANT_ACCESS_PARAM)?.trim() ?? '');
|
||||||
|
const isOpen = $derived(Boolean(studentEmail));
|
||||||
|
|
||||||
|
let isSubmitting = $state(false);
|
||||||
|
|
||||||
|
function closeModal() {
|
||||||
|
const nextParams = new URLSearchParams(page.url.search);
|
||||||
|
nextParams.delete(GRANT_ACCESS_PARAM);
|
||||||
|
const query = nextParams.toString();
|
||||||
|
|
||||||
|
goto(resolve(`${page.url.pathname}${query ? `?${query}` : ''}`, {}));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleSendInvite() {
|
||||||
|
if (!courseId || !studentEmail || $isStudentLimitReached) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isSubmitting = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await orgApi.importAudienceMembers({
|
||||||
|
recipientCsv: studentEmail,
|
||||||
|
courseIds: [courseId],
|
||||||
|
sendEmail: true
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await courseApi.refreshCourse(courseId, $profile.id);
|
||||||
|
closeModal();
|
||||||
|
} finally {
|
||||||
|
isSubmitting = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<Dialog.Root
|
||||||
|
open={isOpen}
|
||||||
|
onOpenChange={(open) => {
|
||||||
|
if (!open) closeModal();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Dialog.Content class="max-w-md">
|
||||||
|
<Dialog.Header>
|
||||||
|
<Dialog.Title>{$t('course.navItem.people.grant_access_modal.title')}</Dialog.Title>
|
||||||
|
<Dialog.Description>
|
||||||
|
{$t('course.navItem.people.grant_access_modal.description', { email: studentEmail })}
|
||||||
|
</Dialog.Description>
|
||||||
|
</Dialog.Header>
|
||||||
|
|
||||||
|
{#if $isStudentLimitReached}
|
||||||
|
<UpgradeBanner removeParams={[GRANT_ACCESS_PARAM]}>
|
||||||
|
{$t('course.navItem.people.invite_modal.student_limit_reached')}
|
||||||
|
</UpgradeBanner>
|
||||||
|
{/if}
|
||||||
|
|
||||||
|
<div class="flex justify-end gap-2">
|
||||||
|
<Button type="button" variant="outline" onclick={closeModal} disabled={isSubmitting}>
|
||||||
|
{$t('course.navItem.people.grant_access_modal.cancel')}
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="secondary"
|
||||||
|
onclick={handleSendInvite}
|
||||||
|
loading={isSubmitting}
|
||||||
|
disabled={$isStudentLimitReached || !studentEmail}
|
||||||
|
>
|
||||||
|
{$t('course.navItem.people.grant_access_modal.send_invite')}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Dialog.Content>
|
||||||
|
</Dialog.Root>
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
import * as Avatar from '@cio/ui/base/avatar';
|
import * as Avatar from '@cio/ui/base/avatar';
|
||||||
import { ComingSoon, RoleBasedSecurity, UpgradeBanner } from '$features/ui';
|
import { ComingSoon, RoleBasedSecurity, UpgradeBanner } from '$features/ui';
|
||||||
import InvitationModal from '$features/course/components/people/invitation-modal.svelte';
|
import InvitationModal from '$features/course/components/people/invitation-modal.svelte';
|
||||||
|
import GrantAccessModal from '$features/course/components/people/grant-access-modal.svelte';
|
||||||
import DeleteConfirmation from '$features/course/components/people/delete-confirmation.svelte';
|
import DeleteConfirmation from '$features/course/components/people/delete-confirmation.svelte';
|
||||||
import { isStudentLimitReached } from '$lib/utils/store/org';
|
import { isStudentLimitReached } from '$lib/utils/store/org';
|
||||||
|
|
||||||
@@ -101,6 +102,7 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<InvitationModal />
|
<InvitationModal />
|
||||||
|
<GrantAccessModal />
|
||||||
|
|
||||||
{#if $isStudentLimitReached}
|
{#if $isStudentLimitReached}
|
||||||
<UpgradeBanner className="mb-2">{$t('course.navItem.people.invite_modal.student_limit_reached')}</UpgradeBanner>
|
<UpgradeBanner className="mb-2">{$t('course.navItem.people.invite_modal.student_limit_reached')}</UpgradeBanner>
|
||||||
|
|||||||
@@ -1198,6 +1198,12 @@
|
|||||||
"no_matching_existing_students": "No organization students match your search.",
|
"no_matching_existing_students": "No organization students match your search.",
|
||||||
"notify_existing_students": "Send course access email",
|
"notify_existing_students": "Send course access email",
|
||||||
"grant_access": "Grant Access",
|
"grant_access": "Grant Access",
|
||||||
|
"grant_access_modal": {
|
||||||
|
"title": "Giv studerende adgang",
|
||||||
|
"description": "Giv {email} adgang til dette kursus. Vi sender en invitationsmail, så vedkommende kan tilslutte sig din organisation og tilmelde sig kurset.",
|
||||||
|
"cancel": "Annuller",
|
||||||
|
"send_invite": "Send invitationen"
|
||||||
|
},
|
||||||
"invite_new_students_title": "Invite new students",
|
"invite_new_students_title": "Invite new students",
|
||||||
"invite_new_students_description": "Invite new people into your organization and pre-assign them to this course. They will create an account, accept the invite, and get access.",
|
"invite_new_students_description": "Invite new people into your organization and pre-assign them to this course. They will create an account, accept the invite, and get access.",
|
||||||
"invite_new_students": "Invite Students",
|
"invite_new_students": "Invite Students",
|
||||||
|
|||||||
@@ -1198,6 +1198,12 @@
|
|||||||
"no_matching_existing_students": "No organization students match your search.",
|
"no_matching_existing_students": "No organization students match your search.",
|
||||||
"notify_existing_students": "Send course access email",
|
"notify_existing_students": "Send course access email",
|
||||||
"grant_access": "Grant Access",
|
"grant_access": "Grant Access",
|
||||||
|
"grant_access_modal": {
|
||||||
|
"title": "Schülerzugang gewähren",
|
||||||
|
"description": "Gewähren Sie {email} Zugang zu diesem Kurs. Wir senden eine Einladungs-E-Mail, damit die Person Ihrer Organisation beitreten und in diesem Kurs eingeschrieben werden kann.",
|
||||||
|
"cancel": "Abbrechen",
|
||||||
|
"send_invite": "Einladung senden"
|
||||||
|
},
|
||||||
"invite_new_students_title": "Invite new students",
|
"invite_new_students_title": "Invite new students",
|
||||||
"invite_new_students_description": "Invite new people into your organization and pre-assign them to this course. They will create an account, accept the invite, and get access.",
|
"invite_new_students_description": "Invite new people into your organization and pre-assign them to this course. They will create an account, accept the invite, and get access.",
|
||||||
"invite_new_students": "Invite Students",
|
"invite_new_students": "Invite Students",
|
||||||
|
|||||||
@@ -3230,6 +3230,12 @@
|
|||||||
"no_matching_existing_students": "No organization students match your search.",
|
"no_matching_existing_students": "No organization students match your search.",
|
||||||
"notify_existing_students": "Send course access email",
|
"notify_existing_students": "Send course access email",
|
||||||
"grant_access": "Grant Access",
|
"grant_access": "Grant Access",
|
||||||
|
"grant_access_modal": {
|
||||||
|
"title": "Give student access",
|
||||||
|
"description": "Give {email} access to this course. We'll send them an invite email to join your organization and enroll in this course.",
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"send_invite": "Send invite"
|
||||||
|
},
|
||||||
"invite_new_students_title": "Invite new students",
|
"invite_new_students_title": "Invite new students",
|
||||||
"invite_new_students_description": "Invite new people into your organization and pre-assign them to this course. They will create an account, accept the invite, and get access.",
|
"invite_new_students_description": "Invite new people into your organization and pre-assign them to this course. They will create an account, accept the invite, and get access.",
|
||||||
"invite_new_students": "Invite Students",
|
"invite_new_students": "Invite Students",
|
||||||
|
|||||||
@@ -1198,6 +1198,12 @@
|
|||||||
"no_matching_existing_students": "No organization students match your search.",
|
"no_matching_existing_students": "No organization students match your search.",
|
||||||
"notify_existing_students": "Send course access email",
|
"notify_existing_students": "Send course access email",
|
||||||
"grant_access": "Grant Access",
|
"grant_access": "Grant Access",
|
||||||
|
"grant_access_modal": {
|
||||||
|
"title": "Dar acceso al estudiante",
|
||||||
|
"description": "Da acceso a {email} a este curso. Le enviaremos un correo de invitación para unirse a tu organización e inscribirse en este curso.",
|
||||||
|
"cancel": "Cancelar",
|
||||||
|
"send_invite": "Enviar invitación"
|
||||||
|
},
|
||||||
"invite_new_students_title": "Invite new students",
|
"invite_new_students_title": "Invite new students",
|
||||||
"invite_new_students_description": "Invite new people into your organization and pre-assign them to this course. They will create an account, accept the invite, and get access.",
|
"invite_new_students_description": "Invite new people into your organization and pre-assign them to this course. They will create an account, accept the invite, and get access.",
|
||||||
"invite_new_students": "Invite Students",
|
"invite_new_students": "Invite Students",
|
||||||
|
|||||||
@@ -1198,6 +1198,12 @@
|
|||||||
"no_matching_existing_students": "No organization students match your search.",
|
"no_matching_existing_students": "No organization students match your search.",
|
||||||
"notify_existing_students": "Send course access email",
|
"notify_existing_students": "Send course access email",
|
||||||
"grant_access": "Grant Access",
|
"grant_access": "Grant Access",
|
||||||
|
"grant_access_modal": {
|
||||||
|
"title": "Accorder l'accès à l'étudiant",
|
||||||
|
"description": "Accordez à {email} l'accès à ce cours. Nous lui enverrons un e-mail d'invitation pour rejoindre votre organisation et s'inscrire à ce cours.",
|
||||||
|
"cancel": "Annuler",
|
||||||
|
"send_invite": "Envoyer l'invitation"
|
||||||
|
},
|
||||||
"invite_new_students_title": "Invite new students",
|
"invite_new_students_title": "Invite new students",
|
||||||
"invite_new_students_description": "Invite new people into your organization and pre-assign them to this course. They will create an account, accept the invite, and get access.",
|
"invite_new_students_description": "Invite new people into your organization and pre-assign them to this course. They will create an account, accept the invite, and get access.",
|
||||||
"invite_new_students": "Invite Students",
|
"invite_new_students": "Invite Students",
|
||||||
|
|||||||
@@ -1198,6 +1198,12 @@
|
|||||||
"no_matching_existing_students": "No organization students match your search.",
|
"no_matching_existing_students": "No organization students match your search.",
|
||||||
"notify_existing_students": "Send course access email",
|
"notify_existing_students": "Send course access email",
|
||||||
"grant_access": "Grant Access",
|
"grant_access": "Grant Access",
|
||||||
|
"grant_access_modal": {
|
||||||
|
"title": "छात्र को एक्सेस दें",
|
||||||
|
"description": "{email} को इस कोर्स का एक्सेस दें। हम उन्हें आपके संगठन में शामिल होने और इस कोर्स में नामांकन के लिए आमंत्रण ईमेल भेजेंगे।",
|
||||||
|
"cancel": "रद्द करें",
|
||||||
|
"send_invite": "निमंत्रण भेजें"
|
||||||
|
},
|
||||||
"invite_new_students_title": "Invite new students",
|
"invite_new_students_title": "Invite new students",
|
||||||
"invite_new_students_description": "Invite new people into your organization and pre-assign them to this course. They will create an account, accept the invite, and get access.",
|
"invite_new_students_description": "Invite new people into your organization and pre-assign them to this course. They will create an account, accept the invite, and get access.",
|
||||||
"invite_new_students": "Invite Students",
|
"invite_new_students": "Invite Students",
|
||||||
|
|||||||
@@ -1198,6 +1198,12 @@
|
|||||||
"no_matching_existing_students": "No organization students match your search.",
|
"no_matching_existing_students": "No organization students match your search.",
|
||||||
"notify_existing_students": "Send course access email",
|
"notify_existing_students": "Send course access email",
|
||||||
"grant_access": "Grant Access",
|
"grant_access": "Grant Access",
|
||||||
|
"grant_access_modal": {
|
||||||
|
"title": "Udostępnij dostęp studentowi",
|
||||||
|
"description": "Przyznaj {email} dostęp do tego kursu. Wyślemy zaproszenie e-mailem, aby dołączył do Twojej organizacji i zapisał się na ten kurs.",
|
||||||
|
"cancel": "Anuluj",
|
||||||
|
"send_invite": "Wyślij zaproszenie"
|
||||||
|
},
|
||||||
"invite_new_students_title": "Invite new students",
|
"invite_new_students_title": "Invite new students",
|
||||||
"invite_new_students_description": "Invite new people into your organization and pre-assign them to this course. They will create an account, accept the invite, and get access.",
|
"invite_new_students_description": "Invite new people into your organization and pre-assign them to this course. They will create an account, accept the invite, and get access.",
|
||||||
"invite_new_students": "Invite Students",
|
"invite_new_students": "Invite Students",
|
||||||
|
|||||||
@@ -1198,6 +1198,12 @@
|
|||||||
"no_matching_existing_students": "No organization students match your search.",
|
"no_matching_existing_students": "No organization students match your search.",
|
||||||
"notify_existing_students": "Send course access email",
|
"notify_existing_students": "Send course access email",
|
||||||
"grant_access": "Grant Access",
|
"grant_access": "Grant Access",
|
||||||
|
"grant_access_modal": {
|
||||||
|
"title": "Conceder acesso ao aluno",
|
||||||
|
"description": "Conceda acesso a {email} a este curso. Enviaremos um e-mail de convite para que a pessoa entre na sua organização e se inscreva neste curso.",
|
||||||
|
"cancel": "Cancelar",
|
||||||
|
"send_invite": "Enviar convite"
|
||||||
|
},
|
||||||
"invite_new_students_title": "Invite new students",
|
"invite_new_students_title": "Invite new students",
|
||||||
"invite_new_students_description": "Invite new people into your organization and pre-assign them to this course. They will create an account, accept the invite, and get access.",
|
"invite_new_students_description": "Invite new people into your organization and pre-assign them to this course. They will create an account, accept the invite, and get access.",
|
||||||
"invite_new_students": "Invite Students",
|
"invite_new_students": "Invite Students",
|
||||||
|
|||||||
@@ -1198,6 +1198,12 @@
|
|||||||
"no_matching_existing_students": "No organization students match your search.",
|
"no_matching_existing_students": "No organization students match your search.",
|
||||||
"notify_existing_students": "Send course access email",
|
"notify_existing_students": "Send course access email",
|
||||||
"grant_access": "Grant Access",
|
"grant_access": "Grant Access",
|
||||||
|
"grant_access_modal": {
|
||||||
|
"title": "Предоставить доступ студенту",
|
||||||
|
"description": "Предоставьте {email} доступ к этому курсу. Мы отправим приглашение по электронной почте, чтобы присоединиться к вашей организации и записаться на курс.",
|
||||||
|
"cancel": "Отмена",
|
||||||
|
"send_invite": "Отправить приглашение"
|
||||||
|
},
|
||||||
"invite_new_students_title": "Invite new students",
|
"invite_new_students_title": "Invite new students",
|
||||||
"invite_new_students_description": "Invite new people into your organization and pre-assign them to this course. They will create an account, accept the invite, and get access.",
|
"invite_new_students_description": "Invite new people into your organization and pre-assign them to this course. They will create an account, accept the invite, and get access.",
|
||||||
"invite_new_students": "Invite Students",
|
"invite_new_students": "Invite Students",
|
||||||
|
|||||||
@@ -1198,6 +1198,12 @@
|
|||||||
"no_matching_existing_students": "No organization students match your search.",
|
"no_matching_existing_students": "No organization students match your search.",
|
||||||
"notify_existing_students": "Send course access email",
|
"notify_existing_students": "Send course access email",
|
||||||
"grant_access": "Grant Access",
|
"grant_access": "Grant Access",
|
||||||
|
"grant_access_modal": {
|
||||||
|
"title": "Cấp quyền truy cập cho học viên",
|
||||||
|
"description": "Cấp cho {email} quyền truy cập khóa học này. Chúng tôi sẽ gửi email mời để tham gia tổ chức của bạn và đăng ký khóa học.",
|
||||||
|
"cancel": "Hủy",
|
||||||
|
"send_invite": "Gửi lời mời"
|
||||||
|
},
|
||||||
"invite_new_students_title": "Invite new students",
|
"invite_new_students_title": "Invite new students",
|
||||||
"invite_new_students_description": "Invite new people into your organization and pre-assign them to this course. They will create an account, accept the invite, and get access.",
|
"invite_new_students_description": "Invite new people into your organization and pre-assign them to this course. They will create an account, accept the invite, and get access.",
|
||||||
"invite_new_students": "Invite Students",
|
"invite_new_students": "Invite Students",
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ export const teacherStudentBuyRequestEmail = defineEmail({
|
|||||||
courseName: z.string().min(1),
|
courseName: z.string().min(1),
|
||||||
studentEmail: z.string().email(),
|
studentEmail: z.string().email(),
|
||||||
studentFullname: z.string().min(1),
|
studentFullname: z.string().min(1),
|
||||||
|
courseUrl: z.url(),
|
||||||
|
autoEnrollUrl: z.url(),
|
||||||
branding: ZEmailBranding
|
branding: ZEmailBranding
|
||||||
}),
|
}),
|
||||||
render: (fields) => {
|
render: (fields) => {
|
||||||
@@ -22,6 +24,19 @@ export const teacherStudentBuyRequestEmail = defineEmail({
|
|||||||
Name: ${fields.studentFullname}<br />
|
Name: ${fields.studentFullname}<br />
|
||||||
Email: ${fields.studentEmail}
|
Email: ${fields.studentEmail}
|
||||||
</p>
|
</p>
|
||||||
|
<p style="font-weight: bold; margin-top: 24px;">How to give this student access</p>
|
||||||
|
<ol style="padding-left: 20px; line-height: 1.6;">
|
||||||
|
<li>Open the course: <a href="${fields.courseUrl}">${fields.courseUrl}</a></li>
|
||||||
|
<li>Click <strong>People</strong> on the sidebar</li>
|
||||||
|
<li>Click <strong>Add</strong></li>
|
||||||
|
<li>Under <strong>Invite new students</strong>, enter <strong>${fields.studentEmail}</strong></li>
|
||||||
|
<li>Click <strong>Invite Students</strong></li>
|
||||||
|
</ol>
|
||||||
|
<p style="font-weight: bold; margin-top: 24px;">Or automatically enroll them</p>
|
||||||
|
<p>Use the button below to open the course and send this student an invite in one step.</p>
|
||||||
|
<div>
|
||||||
|
<a class="button" href="${fields.autoEnrollUrl}">Auto Enroll Student</a>
|
||||||
|
</div>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
return getDefaultTemplate(content, fields.branding);
|
return getDefaultTemplate(content, fields.branding);
|
||||||
|
|||||||
Reference in New Issue
Block a user