ENCOA-316 ENCOA-317:
Refactor components to remove Layout wrapper and pass it in the App component , implemented a skeleton feedback while loading page and improved API calls related to Dashboard/User Profile
This commit is contained in:
@@ -1,18 +1,14 @@
|
||||
/* eslint-disable @next/next/no-img-element */
|
||||
import Layout from "@/components/High/Layout";
|
||||
import useGroups from "@/hooks/useGroups";
|
||||
import usePackages from "@/hooks/usePackages";
|
||||
import useUsers from "@/hooks/useUsers";
|
||||
import { User } from "@/interfaces/user";
|
||||
import clsx from "clsx";
|
||||
import { capitalize, sortBy } from "lodash";
|
||||
import { capitalize } from "lodash";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import useInvites from "@/hooks/useInvites";
|
||||
import { BsArrowRepeat } from "react-icons/bs";
|
||||
import InviteCard from "@/components/Medium/InviteCard";
|
||||
import { useRouter } from "next/router";
|
||||
import { ToastContainer } from "react-toastify";
|
||||
import useDiscounts from "@/hooks/useDiscounts";
|
||||
import PaymobPayment from "@/components/PaymobPayment";
|
||||
import moment from "moment";
|
||||
import { EntityWithRoles } from "@/interfaces/entity";
|
||||
@@ -22,241 +18,345 @@ import { useAllowedEntities } from "@/hooks/useEntityPermissions";
|
||||
import Select from "@/components/Low/Select";
|
||||
|
||||
interface Props {
|
||||
user: User
|
||||
discounts: Discount[]
|
||||
packages: Package[]
|
||||
entities: EntityWithRoles[]
|
||||
hasExpired?: boolean;
|
||||
reload: () => void;
|
||||
user: User;
|
||||
discounts: Discount[];
|
||||
packages: Package[];
|
||||
entities: EntityWithRoles[];
|
||||
hasExpired?: boolean;
|
||||
reload: () => void;
|
||||
}
|
||||
|
||||
export default function PaymentDue({ user, discounts = [], entities = [], packages = [], hasExpired = false, reload }: Props) {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [entity, setEntity] = useState<EntityWithRoles>()
|
||||
export default function PaymentDue({
|
||||
user,
|
||||
discounts = [],
|
||||
entities = [],
|
||||
packages = [],
|
||||
hasExpired = false,
|
||||
reload,
|
||||
}: Props) {
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [entity, setEntity] = useState<EntityWithRoles>();
|
||||
|
||||
const router = useRouter();
|
||||
const router = useRouter();
|
||||
|
||||
const { users } = useUsers();
|
||||
const { invites, isLoading: isInvitesLoading, reload: reloadInvites } = useInvites({ to: user?.id });
|
||||
const { users } = useUsers();
|
||||
const {
|
||||
invites,
|
||||
isLoading: isInvitesLoading,
|
||||
reload: reloadInvites,
|
||||
} = useInvites({ to: user?.id });
|
||||
|
||||
const isIndividual = useMemo(() => {
|
||||
if (isAdmin(user)) return false;
|
||||
if (user?.type !== "student") return false;
|
||||
const isIndividual = useMemo(() => {
|
||||
if (isAdmin(user)) return false;
|
||||
if (user?.type !== "student") return false;
|
||||
|
||||
return user.entities.length === 0
|
||||
}, [user])
|
||||
return user.entities.length === 0;
|
||||
}, [user]);
|
||||
|
||||
const appliedDiscount = useMemo(() => {
|
||||
const biggestDiscount = [...discounts].sort((a, b) => b.percentage - a.percentage).shift();
|
||||
const appliedDiscount = useMemo(() => {
|
||||
const biggestDiscount = [...discounts]
|
||||
.sort((a, b) => b.percentage - a.percentage)
|
||||
.shift();
|
||||
|
||||
if (!biggestDiscount || (biggestDiscount.validUntil && moment(biggestDiscount.validUntil).isBefore(moment())))
|
||||
return 0;
|
||||
if (
|
||||
!biggestDiscount ||
|
||||
(biggestDiscount.validUntil &&
|
||||
moment(biggestDiscount.validUntil).isBefore(moment()))
|
||||
)
|
||||
return 0;
|
||||
|
||||
return biggestDiscount.percentage
|
||||
}, [discounts])
|
||||
return biggestDiscount.percentage;
|
||||
}, [discounts]);
|
||||
|
||||
const entitiesThatCanBePaid = useAllowedEntities(user, entities, 'pay_entity')
|
||||
const entitiesThatCanBePaid = useAllowedEntities(
|
||||
user,
|
||||
entities,
|
||||
"pay_entity"
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (entitiesThatCanBePaid.length > 0) setEntity(entitiesThatCanBePaid[0])
|
||||
}, [entitiesThatCanBePaid])
|
||||
useEffect(() => {
|
||||
if (entitiesThatCanBePaid.length > 0) setEntity(entitiesThatCanBePaid[0]);
|
||||
}, [entitiesThatCanBePaid]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<ToastContainer />
|
||||
{isLoading && (
|
||||
<div className="absolute left-0 top-0 z-[999] h-screen w-screen overflow-hidden bg-black/60">
|
||||
<div className="absolute left-1/2 top-1/2 flex h-fit w-fit -translate-x-1/2 -translate-y-1/2 flex-col items-center gap-8 text-white">
|
||||
<span className={clsx("loading loading-infinity w-48 animate-pulse")} />
|
||||
<span className={clsx("text-2xl font-bold animate-pulse")}>Completing your payment...</span>
|
||||
<span>If you canceled your payment or it failed, please click the button below to restart</span>
|
||||
<button
|
||||
onClick={() => setIsLoading(false)}
|
||||
className="border border-white rounded-full px-4 py-2 hover:bg-white/80 hover:text-black cursor-pointer transition ease-in-out duration-300">
|
||||
Cancel Payment
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<Layout user={user} navDisabled={hasExpired}>
|
||||
{invites.length > 0 && (
|
||||
<section className="flex flex-col gap-1 md:gap-3">
|
||||
<div className="flex items-center gap-4">
|
||||
<div
|
||||
onClick={reloadInvites}
|
||||
className="text-mti-purple-light hover:text-mti-purple-dark flex cursor-pointer items-center gap-2 transition duration-300 ease-in-out">
|
||||
<span className="text-mti-black text-lg font-bold">Invites</span>
|
||||
<BsArrowRepeat className={clsx("text-xl", isInvitesLoading && "animate-spin")} />
|
||||
</div>
|
||||
</div>
|
||||
<span className="text-mti-gray-taupe scrollbar-hide flex gap-8 overflow-x-scroll">
|
||||
{invites.map((invite) => (
|
||||
<InviteCard
|
||||
key={invite.id}
|
||||
invite={invite}
|
||||
users={users}
|
||||
reload={() => {
|
||||
reloadInvites();
|
||||
router.reload();
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</span>
|
||||
</section>
|
||||
)}
|
||||
return (
|
||||
<>
|
||||
<ToastContainer />
|
||||
{isLoading && (
|
||||
<div className="absolute left-0 top-0 z-[999] h-screen w-screen overflow-hidden bg-black/60">
|
||||
<div className="absolute left-1/2 top-1/2 flex h-fit w-fit -translate-x-1/2 -translate-y-1/2 flex-col items-center gap-8 text-white">
|
||||
<span
|
||||
className={clsx("loading loading-infinity w-48 animate-pulse")}
|
||||
/>
|
||||
<span className={clsx("text-2xl font-bold animate-pulse")}>
|
||||
Completing your payment...
|
||||
</span>
|
||||
<span>
|
||||
If you canceled your payment or it failed, please click the button
|
||||
below to restart
|
||||
</span>
|
||||
<button
|
||||
onClick={() => setIsLoading(false)}
|
||||
className="border border-white rounded-full px-4 py-2 hover:bg-white/80 hover:text-black cursor-pointer transition ease-in-out duration-300"
|
||||
>
|
||||
Cancel Payment
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<>
|
||||
{invites.length > 0 && (
|
||||
<section className="flex flex-col gap-1 md:gap-3">
|
||||
<div className="flex items-center gap-4">
|
||||
<div
|
||||
onClick={reloadInvites}
|
||||
className="text-mti-purple-light hover:text-mti-purple-dark flex cursor-pointer items-center gap-2 transition duration-300 ease-in-out"
|
||||
>
|
||||
<span className="text-mti-black text-lg font-bold">
|
||||
Invites
|
||||
</span>
|
||||
<BsArrowRepeat
|
||||
className={clsx(
|
||||
"text-xl",
|
||||
isInvitesLoading && "animate-spin"
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<span className="text-mti-gray-taupe scrollbar-hide flex gap-8 overflow-x-scroll">
|
||||
{invites.map((invite) => (
|
||||
<InviteCard
|
||||
key={invite.id}
|
||||
invite={invite}
|
||||
users={users}
|
||||
reload={() => {
|
||||
reloadInvites();
|
||||
router.reload();
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</span>
|
||||
</section>
|
||||
)}
|
||||
|
||||
<div className="flex w-full flex-col items-center justify-center gap-4 text-center">
|
||||
{hasExpired && <span className="text-lg font-bold">You do not have time credits for your account type!</span>}
|
||||
{isIndividual && (
|
||||
<div className="scrollbar-hide flex w-full flex-col items-center gap-12 overflow-x-scroll">
|
||||
<span className="max-w-lg">
|
||||
To add to your use of EnCoach, please purchase one of the time packages available below:
|
||||
</span>
|
||||
<div className="flex w-full flex-wrap justify-center gap-8">
|
||||
{packages.map((p) => (
|
||||
<div key={p.id} className={clsx("flex flex-col items-start gap-6 rounded-xl bg-white p-4")}>
|
||||
<div className="mb-2 flex flex-col items-start">
|
||||
<img src="/logo_title.png" alt="EnCoach's Logo" className="w-32" />
|
||||
<span className="text-xl font-semibold">
|
||||
EnCoach - {p.duration}{" "}
|
||||
{capitalize(
|
||||
p.duration === 1 ? p.duration_unit.slice(0, p.duration_unit.length - 1) : p.duration_unit,
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex w-full flex-col items-start gap-2">
|
||||
{appliedDiscount === 0 && (
|
||||
<span className="text-2xl">
|
||||
{p.price} {p.currency}
|
||||
</span>
|
||||
)}
|
||||
{appliedDiscount > 0 && (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-2xl line-through">
|
||||
{p.price} {p.currency}
|
||||
</span>
|
||||
<span className="text-2xl text-mti-red-light">
|
||||
{(p.price - p.price * (appliedDiscount / 100)).toFixed(2)} {p.currency}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<PaymobPayment
|
||||
user={user}
|
||||
setIsPaymentLoading={setIsLoading}
|
||||
onSuccess={() => {
|
||||
setTimeout(reload, 500);
|
||||
}}
|
||||
currency={p.currency}
|
||||
duration={p.duration}
|
||||
duration_unit={p.duration_unit}
|
||||
price={+(p.price - p.price * (appliedDiscount / 100)).toFixed(2)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col items-start gap-1">
|
||||
<span>This includes:</span>
|
||||
<ul className="flex flex-col items-start text-sm">
|
||||
<li>- Train your abilities for the IELTS exam</li>
|
||||
<li>- Gain insights into your weaknesses and strengths</li>
|
||||
<li>- Allow yourself to correctly prepare for the exam</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="flex w-full flex-col items-center justify-center gap-4 text-center">
|
||||
{hasExpired && (
|
||||
<span className="text-lg font-bold">
|
||||
You do not have time credits for your account type!
|
||||
</span>
|
||||
)}
|
||||
{isIndividual && (
|
||||
<div className="scrollbar-hide flex w-full flex-col items-center gap-12 overflow-x-scroll">
|
||||
<span className="max-w-lg">
|
||||
To add to your use of EnCoach, please purchase one of the time
|
||||
packages available below:
|
||||
</span>
|
||||
<div className="flex w-full flex-wrap justify-center gap-8">
|
||||
{packages.map((p) => (
|
||||
<div
|
||||
key={p.id}
|
||||
className={clsx(
|
||||
"flex flex-col items-start gap-6 rounded-xl bg-white p-4"
|
||||
)}
|
||||
>
|
||||
<div className="mb-2 flex flex-col items-start">
|
||||
<img
|
||||
src="/logo_title.png"
|
||||
alt="EnCoach's Logo"
|
||||
className="w-32"
|
||||
/>
|
||||
<span className="text-xl font-semibold">
|
||||
EnCoach - {p.duration}{" "}
|
||||
{capitalize(
|
||||
p.duration === 1
|
||||
? p.duration_unit.slice(
|
||||
0,
|
||||
p.duration_unit.length - 1
|
||||
)
|
||||
: p.duration_unit
|
||||
)}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex w-full flex-col items-start gap-2">
|
||||
{appliedDiscount === 0 && (
|
||||
<span className="text-2xl">
|
||||
{p.price} {p.currency}
|
||||
</span>
|
||||
)}
|
||||
{appliedDiscount > 0 && (
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-2xl line-through">
|
||||
{p.price} {p.currency}
|
||||
</span>
|
||||
<span className="text-2xl text-mti-red-light">
|
||||
{(
|
||||
p.price -
|
||||
p.price * (appliedDiscount / 100)
|
||||
).toFixed(2)}{" "}
|
||||
{p.currency}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
<PaymobPayment
|
||||
user={user}
|
||||
setIsPaymentLoading={setIsLoading}
|
||||
onSuccess={() => {
|
||||
setTimeout(reload, 500);
|
||||
}}
|
||||
currency={p.currency}
|
||||
duration={p.duration}
|
||||
duration_unit={p.duration_unit}
|
||||
price={
|
||||
+(
|
||||
p.price -
|
||||
p.price * (appliedDiscount / 100)
|
||||
).toFixed(2)
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col items-start gap-1">
|
||||
<span>This includes:</span>
|
||||
<ul className="flex flex-col items-start text-sm">
|
||||
<li>- Train your abilities for the IELTS exam</li>
|
||||
<li>
|
||||
- Gain insights into your weaknesses and strengths
|
||||
</li>
|
||||
<li>
|
||||
- Allow yourself to correctly prepare for the exam
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{!isIndividual && entitiesThatCanBePaid.length > 0 &&
|
||||
entity?.payment && (
|
||||
<div className="flex flex-col items-center gap-8">
|
||||
<div className={clsx("flex flex-col items-center gap-4 w-full")}>
|
||||
<label className="font-normal text-base text-mti-gray-dim">Entity</label>
|
||||
<Select
|
||||
defaultValue={{ value: entity?.id, label: entity?.label }}
|
||||
options={entitiesThatCanBePaid.map((e) => ({ value: e.id, label: e.label, entity: e }))}
|
||||
onChange={(e) => e?.value ? setEntity(e?.entity) : null}
|
||||
className="!w-full max-w-[400px] self-center"
|
||||
/>
|
||||
</div>
|
||||
{!isIndividual &&
|
||||
entitiesThatCanBePaid.length > 0 &&
|
||||
entity?.payment && (
|
||||
<div className="flex flex-col items-center gap-8">
|
||||
<div
|
||||
className={clsx("flex flex-col items-center gap-4 w-full")}
|
||||
>
|
||||
<label className="font-normal text-base text-mti-gray-dim">
|
||||
Entity
|
||||
</label>
|
||||
<Select
|
||||
defaultValue={{ value: entity?.id, label: entity?.label }}
|
||||
options={entitiesThatCanBePaid.map((e) => ({
|
||||
value: e.id,
|
||||
label: e.label,
|
||||
entity: e,
|
||||
}))}
|
||||
onChange={(e) => (e?.value ? setEntity(e?.entity) : null)}
|
||||
className="!w-full max-w-[400px] self-center"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<span className="max-w-lg">
|
||||
To add to your use of EnCoach and that of your students and teachers, please pay your designated package
|
||||
below:
|
||||
</span>
|
||||
<div className={clsx("flex flex-col items-start gap-6 rounded-xl bg-white p-4")}>
|
||||
<div className="mb-2 flex flex-col items-start">
|
||||
<img src="/logo_title.png" alt="EnCoach's Logo" className="w-32" />
|
||||
<span className="text-xl font-semibold">
|
||||
EnCoach - {12} Months
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex w-full flex-col items-start gap-2">
|
||||
<span className="text-2xl">
|
||||
{entity.payment.price} {entity.payment.currency}
|
||||
</span>
|
||||
<PaymobPayment
|
||||
user={user}
|
||||
setIsPaymentLoading={setIsLoading}
|
||||
entity={entity}
|
||||
currency={entity.payment.currency}
|
||||
price={entity.payment.price}
|
||||
duration={12}
|
||||
duration_unit="months"
|
||||
onSuccess={() => {
|
||||
setIsLoading(false);
|
||||
setTimeout(reload, 500);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col items-start gap-1">
|
||||
<span>This includes:</span>
|
||||
<ul className="flex flex-col items-start text-sm">
|
||||
<li>
|
||||
- Allow a total of {entity.licenses} students and teachers to use EnCoach
|
||||
</li>
|
||||
<li>- Train their abilities for the IELTS exam</li>
|
||||
<li>- Gain insights into your students' weaknesses and strengths</li>
|
||||
<li>- Allow them to correctly prepare for the exam</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{!isIndividual && entitiesThatCanBePaid.length === 0 && (
|
||||
<div className="flex flex-col items-center">
|
||||
<span className="max-w-lg">
|
||||
You are not the person in charge of your time credits, please contact your administrator about this situation.
|
||||
</span>
|
||||
<span className="max-w-lg">
|
||||
If you believe this to be a mistake, please contact the platform's administration, thank you for your
|
||||
patience.
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{!isIndividual &&
|
||||
entitiesThatCanBePaid.length > 0 &&
|
||||
!entity?.payment && (
|
||||
<div className="flex flex-col items-center gap-8">
|
||||
<div className={clsx("flex flex-col items-center gap-4 w-full")}>
|
||||
<label className="font-normal text-base text-mti-gray-dim">Entity</label>
|
||||
<Select
|
||||
defaultValue={{ value: entity?.id || "", label: entity?.label || "" }}
|
||||
options={entitiesThatCanBePaid.map((e) => ({ value: e.id, label: e.label, entity: e }))}
|
||||
onChange={(e) => e?.value ? setEntity(e?.entity) : null}
|
||||
className="!w-full max-w-[400px] self-center"
|
||||
/>
|
||||
</div>
|
||||
<span className="max-w-lg">
|
||||
An admin nor your agent have yet set the price intended to your requirements in terms of the amount of users
|
||||
you desire and your expected monthly duration.
|
||||
</span>
|
||||
<span className="max-w-lg">
|
||||
Please try again later or contact your agent or an admin, thank you for your patience.
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Layout>
|
||||
</>
|
||||
);
|
||||
<span className="max-w-lg">
|
||||
To add to your use of EnCoach and that of your students and
|
||||
teachers, please pay your designated package below:
|
||||
</span>
|
||||
<div
|
||||
className={clsx(
|
||||
"flex flex-col items-start gap-6 rounded-xl bg-white p-4"
|
||||
)}
|
||||
>
|
||||
<div className="mb-2 flex flex-col items-start">
|
||||
<img
|
||||
src="/logo_title.png"
|
||||
alt="EnCoach's Logo"
|
||||
className="w-32"
|
||||
/>
|
||||
<span className="text-xl font-semibold">
|
||||
EnCoach - {12} Months
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex w-full flex-col items-start gap-2">
|
||||
<span className="text-2xl">
|
||||
{entity.payment.price} {entity.payment.currency}
|
||||
</span>
|
||||
<PaymobPayment
|
||||
user={user}
|
||||
setIsPaymentLoading={setIsLoading}
|
||||
entity={entity}
|
||||
currency={entity.payment.currency}
|
||||
price={entity.payment.price}
|
||||
duration={12}
|
||||
duration_unit="months"
|
||||
onSuccess={() => {
|
||||
setIsLoading(false);
|
||||
setTimeout(reload, 500);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col items-start gap-1">
|
||||
<span>This includes:</span>
|
||||
<ul className="flex flex-col items-start text-sm">
|
||||
<li>
|
||||
- Allow a total of {entity.licenses} students and
|
||||
teachers to use EnCoach
|
||||
</li>
|
||||
<li>- Train their abilities for the IELTS exam</li>
|
||||
<li>
|
||||
- Gain insights into your students' weaknesses and
|
||||
strengths
|
||||
</li>
|
||||
<li>- Allow them to correctly prepare for the exam</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
{!isIndividual && entitiesThatCanBePaid.length === 0 && (
|
||||
<div className="flex flex-col items-center">
|
||||
<span className="max-w-lg">
|
||||
You are not the person in charge of your time credits, please
|
||||
contact your administrator about this situation.
|
||||
</span>
|
||||
<span className="max-w-lg">
|
||||
If you believe this to be a mistake, please contact the
|
||||
platform's administration, thank you for your patience.
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
{!isIndividual &&
|
||||
entitiesThatCanBePaid.length > 0 &&
|
||||
!entity?.payment && (
|
||||
<div className="flex flex-col items-center gap-8">
|
||||
<div
|
||||
className={clsx("flex flex-col items-center gap-4 w-full")}
|
||||
>
|
||||
<label className="font-normal text-base text-mti-gray-dim">
|
||||
Entity
|
||||
</label>
|
||||
<Select
|
||||
defaultValue={{
|
||||
value: entity?.id || "",
|
||||
label: entity?.label || "",
|
||||
}}
|
||||
options={entitiesThatCanBePaid.map((e) => ({
|
||||
value: e.id,
|
||||
label: e.label,
|
||||
entity: e,
|
||||
}))}
|
||||
onChange={(e) => (e?.value ? setEntity(e?.entity) : null)}
|
||||
className="!w-full max-w-[400px] self-center"
|
||||
/>
|
||||
</div>
|
||||
<span className="max-w-lg">
|
||||
An admin nor your agent have yet set the price intended to
|
||||
your requirements in terms of the amount of users you desire
|
||||
and your expected monthly duration.
|
||||
</span>
|
||||
<span className="max-w-lg">
|
||||
Please try again later or contact your agent or an admin,
|
||||
thank you for your patience.
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user