mirror of
https://github.com/Sendouc/sendou.ink.git
synced 2026-05-14 15:00:54 -05:00
Replace migrate group solution
This commit is contained in:
parent
c802faf151
commit
072fdca641
|
|
@ -266,7 +266,6 @@ function ModeOnlyGrid({
|
|||
);
|
||||
}
|
||||
|
||||
// xxx: maybe we should just have a shared custom component for stage image + label
|
||||
function StageTile({
|
||||
option,
|
||||
type,
|
||||
|
|
|
|||
|
|
@ -1,9 +1,10 @@
|
|||
import { Check, Clock, X } from "lucide-react";
|
||||
import { Check, Clock, RotateCcw, X } from "lucide-react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import type { FetcherWithComponents } from "react-router";
|
||||
import { type FetcherWithComponents, Link } from "react-router";
|
||||
import { Avatar } from "~/components/Avatar";
|
||||
import { SendouButton } from "~/components/elements/Button";
|
||||
import { FormWithConfirm } from "~/components/FormWithConfirm";
|
||||
import { SENDOUQ_LOOKING_PAGE } from "~/utils/urls";
|
||||
import * as RejoinVote from "../core/RejoinVote";
|
||||
import styles from "./RematchVotePanel.module.css";
|
||||
|
||||
|
|
@ -37,10 +38,18 @@ export function RematchVotePanel({
|
|||
members.map((m) => m.id),
|
||||
).length;
|
||||
|
||||
const voteResolved = RejoinVote.result(votes).type === "RESOLVED";
|
||||
const viewerVotedYes =
|
||||
RejoinVote.userContinueStatus(votes, viewerUserId) === true;
|
||||
const viewerVotedNo =
|
||||
RejoinVote.userContinueStatus(votes, viewerUserId) === false;
|
||||
|
||||
return (
|
||||
<div className={styles.root}>
|
||||
<div className={styles.prompt}>
|
||||
{t("q:match.rematch.prompt", { count: currentRoundSize })}
|
||||
{voteResolved
|
||||
? t("q:match.rematch.resolved", { count: currentRoundSize })
|
||||
: t("q:match.rematch.prompt", { count: currentRoundSize })}
|
||||
</div>
|
||||
<ul className={styles.list}>
|
||||
{members.map((member) => {
|
||||
|
|
@ -54,7 +63,15 @@ export function RematchVotePanel({
|
|||
);
|
||||
})}
|
||||
</ul>
|
||||
{RejoinVote.userContinueStatus(votes, viewerUserId) === false ? null : (
|
||||
{voteResolved && viewerVotedYes ? (
|
||||
<div className={styles.buttons}>
|
||||
<Link to={SENDOUQ_LOOKING_PAGE}>
|
||||
<SendouButton variant="primary" size="small" icon={<RotateCcw />}>
|
||||
{t("q:match.rematch.backToQueue")}
|
||||
</SendouButton>
|
||||
</Link>
|
||||
</div>
|
||||
) : viewerVotedNo ? null : (
|
||||
<div className={styles.buttons}>
|
||||
<FormWithConfirm
|
||||
fields={[
|
||||
|
|
@ -76,10 +93,7 @@ export function RematchVotePanel({
|
|||
<SendouButton
|
||||
variant="primary"
|
||||
size="small"
|
||||
isDisabled={
|
||||
isPending ||
|
||||
RejoinVote.userContinueStatus(votes, viewerUserId) === true
|
||||
}
|
||||
isDisabled={isPending || viewerVotedYes}
|
||||
onPress={() =>
|
||||
fetcher.submit(
|
||||
{
|
||||
|
|
|
|||
|
|
@ -210,10 +210,14 @@ function RequeueTab({
|
|||
reporterSide !== null &&
|
||||
reporterSide !== viewerSide;
|
||||
|
||||
const showTimeline = !data.match.isLocked;
|
||||
|
||||
return (
|
||||
<SendouTabPanel id={TAB_KEYS.ACTION}>
|
||||
{isStaffOnly || !viewerGroup || !user ? (
|
||||
<MatchTimeline compact teams={teams} score={score} maps={maps} />
|
||||
showTimeline ? (
|
||||
<MatchTimeline compact teams={teams} score={score} maps={maps} />
|
||||
) : null
|
||||
) : (
|
||||
<div className={styles.rematchContent}>
|
||||
{viewerGroup.matchmade ? (
|
||||
|
|
@ -234,7 +238,9 @@ function RequeueTab({
|
|||
) : null}
|
||||
{isOnReporterTeam ? <hr className={styles.divider} /> : null}
|
||||
|
||||
<MatchTimeline compact teams={teams} score={score} maps={maps} />
|
||||
{showTimeline ? (
|
||||
<MatchTimeline compact teams={teams} score={score} maps={maps} />
|
||||
) : null}
|
||||
{isOnConfirmerTeam ? <ScoreConfirmerSection data={data} /> : null}
|
||||
{isOnReporterTeam ? <ReporterUndoSection /> : null}
|
||||
<WeaponReportSection data={data} viewerUserId={user.id} />
|
||||
|
|
|
|||
|
|
@ -4,18 +4,17 @@ import { MatchJoinTab } from "~/components/match-page/MatchJoinTab";
|
|||
import { MatchResultTab } from "~/components/match-page/MatchResultTab";
|
||||
import { MatchRosterTab } from "~/components/match-page/MatchRosterTab";
|
||||
import { MatchTabs } from "~/components/match-page/MatchTabs";
|
||||
import { Redirect } from "~/components/Redirect";
|
||||
import { useUser } from "~/features/auth/core/user";
|
||||
import {
|
||||
resolveActiveRoomLink,
|
||||
useConfirmRoom,
|
||||
} from "~/features/chat/room-link-utils";
|
||||
import { DISPLAY_VOTE_RESULT_SECONDS } from "~/features/sendouq/q-constants";
|
||||
import { ACTION_TAB_AFTER_LOCKED_SECONDS } from "~/features/sendouq/q-constants";
|
||||
import { resolveRoomPass } from "~/features/tournament-match/tournament-match-utils";
|
||||
import { useHasRole } from "~/modules/permissions/hooks";
|
||||
import { databaseTimestampNow } from "~/utils/dates";
|
||||
import { safeNumberParse } from "~/utils/number";
|
||||
import { SENDOUQ_LOOKING_PAGE, sendouQMatchPage, teamPage } from "~/utils/urls";
|
||||
import { sendouQMatchPage, teamPage } from "~/utils/urls";
|
||||
import {
|
||||
resolveTimelineMaps,
|
||||
resolveTimelineSpChanges,
|
||||
|
|
@ -57,17 +56,11 @@ export function SendouQMatchTabs({ data }: { data: SendouQMatchLoaderData }) {
|
|||
const isCanceled = data.match.isCanceled;
|
||||
|
||||
const isParticipant = Boolean(userSide);
|
||||
const migrated = data.migratedToGroupId != null && isParticipant;
|
||||
|
||||
// xxx: hmm is this really correct?
|
||||
if (migrated) {
|
||||
return <Redirect to={SENDOUQ_LOOKING_PAGE} />;
|
||||
}
|
||||
|
||||
const lockedVoteVisible =
|
||||
const lockedActionTabVisible =
|
||||
data.match.confirmedAt !== null &&
|
||||
databaseTimestampNow() <
|
||||
data.match.confirmedAt + DISPLAY_VOTE_RESULT_SECONDS;
|
||||
data.match.confirmedAt + ACTION_TAB_AFTER_LOCKED_SECONDS;
|
||||
|
||||
const matchInProgress = !isLocked && !awaitingConfirmation && currentMap;
|
||||
|
||||
|
|
@ -76,7 +69,7 @@ export function SendouQMatchTabs({ data }: { data: SendouQMatchLoaderData }) {
|
|||
!isCanceled &&
|
||||
(matchInProgress ||
|
||||
awaitingConfirmation ||
|
||||
(isLocked && lockedVoteVisible));
|
||||
(isLocked && lockedActionTabVisible));
|
||||
|
||||
const hasReportedMaps = data.match.mapList.some(
|
||||
(m) => m.winnerGroupId !== null,
|
||||
|
|
|
|||
|
|
@ -40,18 +40,8 @@ export const loader = async ({ params }: LoaderFunctionArgs) => {
|
|||
|
||||
const match = SendouQ.mapMatch(matchUnmapped, user, privateNotes);
|
||||
|
||||
const ownCurrentGroup = user ? SendouQ.findOwnGroup(user.id) : undefined;
|
||||
const migratedToGroupId =
|
||||
ownCurrentGroup &&
|
||||
ownCurrentGroup.id !== match.groupAlpha.id &&
|
||||
ownCurrentGroup.id !== match.groupBravo.id
|
||||
? ownCurrentGroup.id
|
||||
: null;
|
||||
|
||||
return {
|
||||
match,
|
||||
// xxx: i don't think this is the correct idea
|
||||
migratedToGroupId,
|
||||
roomLinks,
|
||||
anyUserPrefersNoSplatnet,
|
||||
reportedWeapons,
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ export const FULL_GROUP_SIZE = 4;
|
|||
|
||||
export const SENDOUQ_BEST_OF = 7;
|
||||
|
||||
export const DISPLAY_VOTE_RESULT_SECONDS = 3 * 60 * 60;
|
||||
export const ACTION_TAB_AFTER_LOCKED_SECONDS = 24 * 60 * 60; // 24 hours
|
||||
|
||||
export const JOIN_CODE_SEARCH_PARAM_KEY = "join";
|
||||
|
||||
|
|
|
|||
|
|
@ -91,7 +91,10 @@ export const loader = async ({ params, request }: LoaderFunctionArgs) => {
|
|||
: null,
|
||||
weapons:
|
||||
info === "weapons"
|
||||
? await ReportedWeaponRepository.seasonReportedWeaponsByUserId({ season, userId: user.id })
|
||||
? await ReportedWeaponRepository.seasonReportedWeaponsByUserId({
|
||||
season,
|
||||
userId: user.id,
|
||||
})
|
||||
: null,
|
||||
players:
|
||||
info === "enemies" || info === "mates"
|
||||
|
|
|
|||
|
|
@ -210,6 +210,7 @@
|
|||
"match.confirmScore": "",
|
||||
"match.confirmScore.wrongHint": "",
|
||||
"match.rematch.prompt": "",
|
||||
"match.rematch.resolved": "",
|
||||
"match.rematch.vote.yes": "",
|
||||
"match.rematch.vote.no": "",
|
||||
"match.rematch.vote.noConfirm": "",
|
||||
|
|
@ -217,6 +218,7 @@
|
|||
"match.rematch.fizzled": "",
|
||||
"match.rematch.waitingCaptain": "",
|
||||
"match.rematch.rejoinQueue": "",
|
||||
"match.rematch.backToQueue": "",
|
||||
"preparing.joinQ": "",
|
||||
"tiers.currentCriteria": "",
|
||||
"tiers.info.p1": "",
|
||||
|
|
|
|||
|
|
@ -210,6 +210,7 @@
|
|||
"match.confirmScore": "",
|
||||
"match.confirmScore.wrongHint": "",
|
||||
"match.rematch.prompt": "",
|
||||
"match.rematch.resolved": "",
|
||||
"match.rematch.vote.yes": "",
|
||||
"match.rematch.vote.no": "",
|
||||
"match.rematch.vote.noConfirm": "",
|
||||
|
|
@ -217,6 +218,7 @@
|
|||
"match.rematch.fizzled": "",
|
||||
"match.rematch.waitingCaptain": "",
|
||||
"match.rematch.rejoinQueue": "",
|
||||
"match.rematch.backToQueue": "",
|
||||
"preparing.joinQ": "",
|
||||
"tiers.currentCriteria": "",
|
||||
"tiers.info.p1": "",
|
||||
|
|
|
|||
|
|
@ -210,6 +210,7 @@
|
|||
"match.confirmScore": "Confirm score",
|
||||
"match.confirmScore.wrongHint": "Wrong score? Ask the other team to undo their report and adjust it.",
|
||||
"match.rematch.prompt": "Continue queueing with the group of {{count}}?",
|
||||
"match.rematch.resolved": "New group of {{count}} formed",
|
||||
"match.rematch.vote.yes": "Yes, continue",
|
||||
"match.rematch.vote.no": "No, I'm done",
|
||||
"match.rematch.vote.noConfirm": "Vote no? You can't change your vote afterwards.",
|
||||
|
|
@ -217,6 +218,7 @@
|
|||
"match.rematch.fizzled": "Nobody wanted to continue",
|
||||
"match.rematch.waitingCaptain": "Waiting for the captain to choose whether to re-queue",
|
||||
"match.rematch.rejoinQueue": "Rejoin queue",
|
||||
"match.rematch.backToQueue": "Back to queue",
|
||||
"preparing.joinQ": "Join the queue",
|
||||
"tiers.currentCriteria": "Current criteria",
|
||||
"tiers.info.p1": "For example, Leviathan is the top 5% of players. Diamond is the 85th percentile etc.",
|
||||
|
|
|
|||
|
|
@ -210,6 +210,7 @@
|
|||
"match.confirmScore": "",
|
||||
"match.confirmScore.wrongHint": "",
|
||||
"match.rematch.prompt": "",
|
||||
"match.rematch.resolved": "",
|
||||
"match.rematch.vote.yes": "",
|
||||
"match.rematch.vote.no": "",
|
||||
"match.rematch.vote.noConfirm": "",
|
||||
|
|
@ -217,6 +218,7 @@
|
|||
"match.rematch.fizzled": "",
|
||||
"match.rematch.waitingCaptain": "",
|
||||
"match.rematch.rejoinQueue": "",
|
||||
"match.rematch.backToQueue": "",
|
||||
"preparing.joinQ": "Unirte a la fila",
|
||||
"tiers.currentCriteria": "Criterios actuales",
|
||||
"tiers.info.p1": "Por ejemplo, Leviathan se encuentra entre el 5% de los mejores jugadores. Diamond es el percentil 85, etc.",
|
||||
|
|
|
|||
|
|
@ -210,6 +210,7 @@
|
|||
"match.confirmScore": "",
|
||||
"match.confirmScore.wrongHint": "",
|
||||
"match.rematch.prompt": "",
|
||||
"match.rematch.resolved": "",
|
||||
"match.rematch.vote.yes": "",
|
||||
"match.rematch.vote.no": "",
|
||||
"match.rematch.vote.noConfirm": "",
|
||||
|
|
@ -217,6 +218,7 @@
|
|||
"match.rematch.fizzled": "",
|
||||
"match.rematch.waitingCaptain": "",
|
||||
"match.rematch.rejoinQueue": "",
|
||||
"match.rematch.backToQueue": "",
|
||||
"preparing.joinQ": "Unirte a la fila",
|
||||
"tiers.currentCriteria": "Criterios actuales",
|
||||
"tiers.info.p1": "Por ejemplo, Leviathan se encuentra entre el 5% de los mejores jugadores. Diamond es el percentil 85, etc.",
|
||||
|
|
|
|||
|
|
@ -210,6 +210,7 @@
|
|||
"match.confirmScore": "",
|
||||
"match.confirmScore.wrongHint": "",
|
||||
"match.rematch.prompt": "",
|
||||
"match.rematch.resolved": "",
|
||||
"match.rematch.vote.yes": "",
|
||||
"match.rematch.vote.no": "",
|
||||
"match.rematch.vote.noConfirm": "",
|
||||
|
|
@ -217,6 +218,7 @@
|
|||
"match.rematch.fizzled": "",
|
||||
"match.rematch.waitingCaptain": "",
|
||||
"match.rematch.rejoinQueue": "",
|
||||
"match.rematch.backToQueue": "",
|
||||
"preparing.joinQ": "",
|
||||
"tiers.currentCriteria": "",
|
||||
"tiers.info.p1": "",
|
||||
|
|
|
|||
|
|
@ -210,6 +210,7 @@
|
|||
"match.confirmScore": "",
|
||||
"match.confirmScore.wrongHint": "",
|
||||
"match.rematch.prompt": "",
|
||||
"match.rematch.resolved": "",
|
||||
"match.rematch.vote.yes": "",
|
||||
"match.rematch.vote.no": "",
|
||||
"match.rematch.vote.noConfirm": "",
|
||||
|
|
@ -217,6 +218,7 @@
|
|||
"match.rematch.fizzled": "",
|
||||
"match.rematch.waitingCaptain": "",
|
||||
"match.rematch.rejoinQueue": "",
|
||||
"match.rematch.backToQueue": "",
|
||||
"preparing.joinQ": "Rejoindre la queue",
|
||||
"tiers.currentCriteria": "Critères actuels",
|
||||
"tiers.info.p1": "Par exemple, Les Léviathans font partie des 5 % des meilleurs joueurs. Le diamant est le top 15%, etc.",
|
||||
|
|
|
|||
|
|
@ -210,6 +210,7 @@
|
|||
"match.confirmScore": "",
|
||||
"match.confirmScore.wrongHint": "",
|
||||
"match.rematch.prompt": "",
|
||||
"match.rematch.resolved": "",
|
||||
"match.rematch.vote.yes": "",
|
||||
"match.rematch.vote.no": "",
|
||||
"match.rematch.vote.noConfirm": "",
|
||||
|
|
@ -217,6 +218,7 @@
|
|||
"match.rematch.fizzled": "",
|
||||
"match.rematch.waitingCaptain": "",
|
||||
"match.rematch.rejoinQueue": "",
|
||||
"match.rematch.backToQueue": "",
|
||||
"preparing.joinQ": "",
|
||||
"tiers.currentCriteria": "",
|
||||
"tiers.info.p1": "",
|
||||
|
|
|
|||
|
|
@ -210,6 +210,7 @@
|
|||
"match.confirmScore": "",
|
||||
"match.confirmScore.wrongHint": "",
|
||||
"match.rematch.prompt": "",
|
||||
"match.rematch.resolved": "",
|
||||
"match.rematch.vote.yes": "",
|
||||
"match.rematch.vote.no": "",
|
||||
"match.rematch.vote.noConfirm": "",
|
||||
|
|
@ -217,6 +218,7 @@
|
|||
"match.rematch.fizzled": "",
|
||||
"match.rematch.waitingCaptain": "",
|
||||
"match.rematch.rejoinQueue": "",
|
||||
"match.rematch.backToQueue": "",
|
||||
"preparing.joinQ": "Unisciti alla coda",
|
||||
"tiers.currentCriteria": "Criterio corrente",
|
||||
"tiers.info.p1": "Per esempio Leviathan è la top 5% dei giocatori. Diamante è l' 85esimo percentile etc.",
|
||||
|
|
|
|||
|
|
@ -210,6 +210,7 @@
|
|||
"match.confirmScore": "",
|
||||
"match.confirmScore.wrongHint": "",
|
||||
"match.rematch.prompt": "",
|
||||
"match.rematch.resolved": "",
|
||||
"match.rematch.vote.yes": "",
|
||||
"match.rematch.vote.no": "",
|
||||
"match.rematch.vote.noConfirm": "",
|
||||
|
|
@ -217,6 +218,7 @@
|
|||
"match.rematch.fizzled": "",
|
||||
"match.rematch.waitingCaptain": "",
|
||||
"match.rematch.rejoinQueue": "",
|
||||
"match.rematch.backToQueue": "",
|
||||
"preparing.joinQ": "列に入る",
|
||||
"tiers.currentCriteria": "現在の基準",
|
||||
"tiers.info.p1": "例として、Leviathanはプレイヤーの上位5%、Diamondは上位15%",
|
||||
|
|
|
|||
|
|
@ -210,6 +210,7 @@
|
|||
"match.confirmScore": "",
|
||||
"match.confirmScore.wrongHint": "",
|
||||
"match.rematch.prompt": "",
|
||||
"match.rematch.resolved": "",
|
||||
"match.rematch.vote.yes": "",
|
||||
"match.rematch.vote.no": "",
|
||||
"match.rematch.vote.noConfirm": "",
|
||||
|
|
@ -217,6 +218,7 @@
|
|||
"match.rematch.fizzled": "",
|
||||
"match.rematch.waitingCaptain": "",
|
||||
"match.rematch.rejoinQueue": "",
|
||||
"match.rematch.backToQueue": "",
|
||||
"preparing.joinQ": "",
|
||||
"tiers.currentCriteria": "",
|
||||
"tiers.info.p1": "",
|
||||
|
|
|
|||
|
|
@ -210,6 +210,7 @@
|
|||
"match.confirmScore": "",
|
||||
"match.confirmScore.wrongHint": "",
|
||||
"match.rematch.prompt": "",
|
||||
"match.rematch.resolved": "",
|
||||
"match.rematch.vote.yes": "",
|
||||
"match.rematch.vote.no": "",
|
||||
"match.rematch.vote.noConfirm": "",
|
||||
|
|
@ -217,6 +218,7 @@
|
|||
"match.rematch.fizzled": "",
|
||||
"match.rematch.waitingCaptain": "",
|
||||
"match.rematch.rejoinQueue": "",
|
||||
"match.rematch.backToQueue": "",
|
||||
"preparing.joinQ": "",
|
||||
"tiers.currentCriteria": "",
|
||||
"tiers.info.p1": "",
|
||||
|
|
|
|||
|
|
@ -210,6 +210,7 @@
|
|||
"match.confirmScore": "",
|
||||
"match.confirmScore.wrongHint": "",
|
||||
"match.rematch.prompt": "",
|
||||
"match.rematch.resolved": "",
|
||||
"match.rematch.vote.yes": "",
|
||||
"match.rematch.vote.no": "",
|
||||
"match.rematch.vote.noConfirm": "",
|
||||
|
|
@ -217,6 +218,7 @@
|
|||
"match.rematch.fizzled": "",
|
||||
"match.rematch.waitingCaptain": "",
|
||||
"match.rematch.rejoinQueue": "",
|
||||
"match.rematch.backToQueue": "",
|
||||
"preparing.joinQ": "",
|
||||
"tiers.currentCriteria": "",
|
||||
"tiers.info.p1": "",
|
||||
|
|
|
|||
|
|
@ -210,6 +210,7 @@
|
|||
"match.confirmScore": "",
|
||||
"match.confirmScore.wrongHint": "",
|
||||
"match.rematch.prompt": "",
|
||||
"match.rematch.resolved": "",
|
||||
"match.rematch.vote.yes": "",
|
||||
"match.rematch.vote.no": "",
|
||||
"match.rematch.vote.noConfirm": "",
|
||||
|
|
@ -217,6 +218,7 @@
|
|||
"match.rematch.fizzled": "",
|
||||
"match.rematch.waitingCaptain": "",
|
||||
"match.rematch.rejoinQueue": "",
|
||||
"match.rematch.backToQueue": "",
|
||||
"preparing.joinQ": "Entrar na fila",
|
||||
"tiers.currentCriteria": "Critérios atuais",
|
||||
"tiers.info.p1": "Por exemplo, Leviathan é o top 5% dos jogadores. Diamond é top 15% e etc.",
|
||||
|
|
|
|||
|
|
@ -210,6 +210,7 @@
|
|||
"match.confirmScore": "",
|
||||
"match.confirmScore.wrongHint": "",
|
||||
"match.rematch.prompt": "",
|
||||
"match.rematch.resolved": "",
|
||||
"match.rematch.vote.yes": "",
|
||||
"match.rematch.vote.no": "",
|
||||
"match.rematch.vote.noConfirm": "",
|
||||
|
|
@ -217,6 +218,7 @@
|
|||
"match.rematch.fizzled": "",
|
||||
"match.rematch.waitingCaptain": "",
|
||||
"match.rematch.rejoinQueue": "",
|
||||
"match.rematch.backToQueue": "",
|
||||
"preparing.joinQ": "Присоединиться к очереди",
|
||||
"tiers.currentCriteria": "Текущие критерии",
|
||||
"tiers.info.p1": "Например, Leviathan - топ 5% игроков, Diamond - 85 процентиль и т.д.",
|
||||
|
|
|
|||
|
|
@ -210,6 +210,7 @@
|
|||
"match.confirmScore": "",
|
||||
"match.confirmScore.wrongHint": "",
|
||||
"match.rematch.prompt": "",
|
||||
"match.rematch.resolved": "",
|
||||
"match.rematch.vote.yes": "",
|
||||
"match.rematch.vote.no": "",
|
||||
"match.rematch.vote.noConfirm": "",
|
||||
|
|
@ -217,6 +218,7 @@
|
|||
"match.rematch.fizzled": "",
|
||||
"match.rematch.waitingCaptain": "",
|
||||
"match.rematch.rejoinQueue": "",
|
||||
"match.rematch.backToQueue": "",
|
||||
"preparing.joinQ": "开始匹配",
|
||||
"tiers.currentCriteria": "当前规则",
|
||||
"tiers.info.p1": "比如说,Leviathan是前5%的玩家,Diamond是前15%的玩家。",
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user