mirror of
https://github.com/makeplane/plane
synced 2024-06-14 14:31:34 +00:00
fix: project loaders for mobx store (#3356)
* add loaders to all the dropdowns outside project wrpper * fix build errors * minor refactor for project states color --------- Co-authored-by: Rahul R <rahulr@Rahuls-MacBook-Pro.local>
This commit is contained in:
parent
f58a00a4ab
commit
d64ae9a2e4
@ -464,7 +464,7 @@ export const ActiveCycleDetails: React.FC<IActiveCycleDetails> = observer((props
|
||||
{
|
||||
issueIds?.filter(
|
||||
(issueId) =>
|
||||
getProjectStates(issueMap[issueId]?.project_id).find(
|
||||
getProjectStates(issueMap[issueId]?.project_id)?.find(
|
||||
(issue) => issue.id === issueMap[issueId]?.state_id
|
||||
)?.group === "completed"
|
||||
)?.length
|
||||
|
@ -129,34 +129,37 @@ export const SelectDuplicateInboxIssueModal: React.FC<Props> = (props) => {
|
||||
<h2 className="mb-2 mt-4 px-3 text-xs font-semibold text-custom-text-100">Select issue</h2>
|
||||
)}
|
||||
<ul className="text-sm text-custom-text-100">
|
||||
{filteredIssues.map((issue) => (
|
||||
<Combobox.Option
|
||||
key={issue.id}
|
||||
as="div"
|
||||
value={issue.id}
|
||||
className={({ active, selected }) =>
|
||||
`flex w-full cursor-pointer select-none items-center gap-2 rounded-md px-3 py-2 text-custom-text-200 ${
|
||||
active || selected ? "bg-custom-background-80 text-custom-text-100" : ""
|
||||
} `
|
||||
}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<span
|
||||
className="block h-1.5 w-1.5 flex-shrink-0 rounded-full"
|
||||
style={{
|
||||
backgroundColor:
|
||||
getProjectStates(issue?.project_id)?.find(
|
||||
(state) => state?.id == issue?.state_id
|
||||
)?.color || "",
|
||||
}}
|
||||
/>
|
||||
<span className="flex-shrink-0 text-xs text-custom-text-200">
|
||||
{getProjectById(issue?.project_id)?.identifier}-{issue.sequence_id}
|
||||
</span>
|
||||
<span className="text-custom-text-200">{issue.name}</span>
|
||||
</div>
|
||||
</Combobox.Option>
|
||||
))}
|
||||
{filteredIssues.map((issue) => {
|
||||
const stateColor =
|
||||
getProjectStates(issue?.project_id)?.find((state) => state?.id == issue?.state_id)
|
||||
?.color || "";
|
||||
|
||||
return (
|
||||
<Combobox.Option
|
||||
key={issue.id}
|
||||
as="div"
|
||||
value={issue.id}
|
||||
className={({ active, selected }) =>
|
||||
`flex w-full cursor-pointer select-none items-center gap-2 rounded-md px-3 py-2 text-custom-text-200 ${
|
||||
active || selected ? "bg-custom-background-80 text-custom-text-100" : ""
|
||||
} `
|
||||
}
|
||||
>
|
||||
<div className="flex items-center gap-2">
|
||||
<span
|
||||
className="block h-1.5 w-1.5 flex-shrink-0 rounded-full"
|
||||
style={{
|
||||
backgroundColor: stateColor,
|
||||
}}
|
||||
/>
|
||||
<span className="flex-shrink-0 text-xs text-custom-text-200">
|
||||
{getProjectById(issue?.project_id)?.identifier}-{issue.sequence_id}
|
||||
</span>
|
||||
<span className="text-custom-text-200">{issue.name}</span>
|
||||
</div>
|
||||
</Combobox.Option>
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</li>
|
||||
) : (
|
||||
|
@ -59,6 +59,10 @@ export const CalendarIssueBlocks: React.FC<Props> = observer((props) => {
|
||||
if (!issues?.[issueId]) return null;
|
||||
|
||||
const issue = issues?.[issueId];
|
||||
|
||||
const stateColor =
|
||||
getProjectStates(issue?.project_id)?.find((state) => state?.id == issue?.state_id)?.color || "";
|
||||
|
||||
return (
|
||||
<Draggable key={issue.id} draggableId={issue.id} index={index}>
|
||||
{(provided, snapshot) => (
|
||||
@ -90,9 +94,7 @@ export const CalendarIssueBlocks: React.FC<Props> = observer((props) => {
|
||||
<span
|
||||
className="h-full w-0.5 flex-shrink-0 rounded"
|
||||
style={{
|
||||
backgroundColor: getProjectStates(issue?.project_id).find(
|
||||
(state) => state?.id == issue?.state_id
|
||||
)?.color,
|
||||
backgroundColor: stateColor,
|
||||
}}
|
||||
/>
|
||||
<div className="flex-shrink-0 text-xs text-custom-text-300">
|
||||
|
@ -22,11 +22,13 @@ export const IssueGanttBlock = ({ data }: { data: TIssue }) => {
|
||||
data.id &&
|
||||
setPeekIssue({ workspaceSlug, projectId: data.project_id, issueId: data.id });
|
||||
|
||||
const stateColor = getProjectStates(data?.project_id)?.find((state) => state?.id == data?.state_id)?.color || "";
|
||||
|
||||
return (
|
||||
<div
|
||||
className="relative flex h-full w-full cursor-pointer items-center rounded"
|
||||
style={{
|
||||
backgroundColor: getProjectStates(data?.project_id)?.find((state) => state?.id == data?.state_id)?.color,
|
||||
backgroundColor: stateColor,
|
||||
}}
|
||||
onClick={handleIssuePeekOverview}
|
||||
>
|
||||
|
@ -30,7 +30,7 @@ export const IssueLabelSelect: React.FC<Props> = observer((props) => {
|
||||
const { workspaceSlug } = router.query;
|
||||
// store hooks
|
||||
const {
|
||||
project: { projectLabels, fetchProjectLabels },
|
||||
project: { getProjectLabels, fetchProjectLabels },
|
||||
} = useLabel();
|
||||
// states
|
||||
const [query, setQuery] = useState("");
|
||||
@ -43,6 +43,9 @@ export const IssueLabelSelect: React.FC<Props> = observer((props) => {
|
||||
const { styles, attributes } = usePopper(referenceElement, popperElement, {
|
||||
placement: "bottom-start",
|
||||
});
|
||||
|
||||
const projectLabels = getProjectLabels(projectId);
|
||||
|
||||
// derived values
|
||||
const filteredOptions =
|
||||
query === "" ? projectLabels : projectLabels?.filter((l) => l.name.toLowerCase().includes(query.toLowerCase()));
|
||||
|
@ -12,6 +12,8 @@ import { IssueService } from "services/issue";
|
||||
import { CycleService } from "services/cycle.service";
|
||||
|
||||
export interface ICycleStore {
|
||||
//Loaders
|
||||
fetchedMap: Record<string, boolean>;
|
||||
// observables
|
||||
cycleMap: Record<string, ICycle>;
|
||||
activeCycleIdMap: Record<string, boolean>;
|
||||
@ -50,6 +52,8 @@ export class CycleStore implements ICycleStore {
|
||||
// observables
|
||||
cycleMap: Record<string, ICycle> = {};
|
||||
activeCycleIdMap: Record<string, boolean> = {};
|
||||
//loaders
|
||||
fetchedMap: Record<string, boolean> = {};
|
||||
// root store
|
||||
rootStore;
|
||||
// services
|
||||
@ -62,6 +66,7 @@ export class CycleStore implements ICycleStore {
|
||||
// observables
|
||||
cycleMap: observable,
|
||||
activeCycleIdMap: observable,
|
||||
fetchedMap: observable,
|
||||
// computed
|
||||
currentProjectCycleIds: computed,
|
||||
currentProjectCompletedCycleIds: computed,
|
||||
@ -96,11 +101,11 @@ export class CycleStore implements ICycleStore {
|
||||
*/
|
||||
get currentProjectCycleIds() {
|
||||
const projectId = this.rootStore.app.router.projectId;
|
||||
if (!projectId) return null;
|
||||
if (!projectId || !this.fetchedMap[projectId]) return null;
|
||||
let allCycles = Object.values(this.cycleMap ?? {}).filter((c) => c?.project === projectId);
|
||||
allCycles = sortBy(allCycles, [(c) => !c.is_favorite, (c) => c.name.toLowerCase()]);
|
||||
const allCycleIds = allCycles.map((c) => c.id);
|
||||
return allCycleIds || null;
|
||||
return allCycleIds;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -108,14 +113,14 @@ export class CycleStore implements ICycleStore {
|
||||
*/
|
||||
get currentProjectCompletedCycleIds() {
|
||||
const projectId = this.rootStore.app.router.projectId;
|
||||
if (!projectId) return null;
|
||||
if (!projectId || !this.fetchedMap[projectId]) return null;
|
||||
let completedCycles = Object.values(this.cycleMap ?? {}).filter((c) => {
|
||||
const hasEndDatePassed = isPast(new Date(c.end_date ?? ""));
|
||||
return c.project === projectId && hasEndDatePassed;
|
||||
});
|
||||
completedCycles = sortBy(completedCycles, [(c) => !c.is_favorite, (c) => c.name.toLowerCase()]);
|
||||
const completedCycleIds = completedCycles.map((c) => c.id);
|
||||
return completedCycleIds || null;
|
||||
return completedCycleIds;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -123,14 +128,14 @@ export class CycleStore implements ICycleStore {
|
||||
*/
|
||||
get currentProjectUpcomingCycleIds() {
|
||||
const projectId = this.rootStore.app.router.projectId;
|
||||
if (!projectId) return null;
|
||||
if (!projectId || !this.fetchedMap[projectId]) return null;
|
||||
let upcomingCycles = Object.values(this.cycleMap ?? {}).filter((c) => {
|
||||
const isStartDateUpcoming = isFuture(new Date(c.start_date ?? ""));
|
||||
return c.project === projectId && isStartDateUpcoming;
|
||||
});
|
||||
upcomingCycles = sortBy(upcomingCycles, [(c) => !c.is_favorite, (c) => c.name.toLowerCase()]);
|
||||
const upcomingCycleIds = upcomingCycles.map((c) => c.id);
|
||||
return upcomingCycleIds || null;
|
||||
return upcomingCycleIds;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -138,14 +143,14 @@ export class CycleStore implements ICycleStore {
|
||||
*/
|
||||
get currentProjectIncompleteCycleIds() {
|
||||
const projectId = this.rootStore.app.router.projectId;
|
||||
if (!projectId) return null;
|
||||
if (!projectId || !this.fetchedMap[projectId]) return null;
|
||||
let incompleteCycles = Object.values(this.cycleMap ?? {}).filter((c) => {
|
||||
const hasEndDatePassed = isPast(new Date(c.end_date ?? ""));
|
||||
return c.project === projectId && !hasEndDatePassed;
|
||||
});
|
||||
incompleteCycles = sortBy(incompleteCycles, [(c) => !c.is_favorite, (c) => c.name.toLowerCase()]);
|
||||
const incompleteCycleIds = incompleteCycles.map((c) => c.id);
|
||||
return incompleteCycleIds || null;
|
||||
return incompleteCycleIds;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -153,13 +158,13 @@ export class CycleStore implements ICycleStore {
|
||||
*/
|
||||
get currentProjectDraftCycleIds() {
|
||||
const projectId = this.rootStore.app.router.projectId;
|
||||
if (!projectId) return null;
|
||||
if (!projectId || !this.fetchedMap[projectId]) return null;
|
||||
let draftCycles = Object.values(this.cycleMap ?? {}).filter(
|
||||
(c) => c.project === projectId && !c.start_date && !c.end_date
|
||||
);
|
||||
draftCycles = sortBy(draftCycles, [(c) => !c.is_favorite, (c) => c.name.toLowerCase()]);
|
||||
const draftCycleIds = draftCycles.map((c) => c.id);
|
||||
return draftCycleIds || null;
|
||||
return draftCycleIds;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -194,6 +199,8 @@ export class CycleStore implements ICycleStore {
|
||||
* @param projectId
|
||||
*/
|
||||
getProjectCycleIds = (projectId: string): string[] | null => {
|
||||
if (!this.fetchedMap[projectId]) return null;
|
||||
|
||||
let cycles = Object.values(this.cycleMap ?? {}).filter((c) => c.project === projectId);
|
||||
cycles = sortBy(cycles, [(c) => !c.is_favorite, (c) => c.name.toLowerCase()]);
|
||||
const cycleIds = cycles.map((c) => c.id);
|
||||
@ -222,6 +229,7 @@ export class CycleStore implements ICycleStore {
|
||||
response.forEach((cycle) => {
|
||||
set(this.cycleMap, [cycle.id], cycle);
|
||||
});
|
||||
set(this.fetchedMap, projectId, true);
|
||||
});
|
||||
return response;
|
||||
});
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { action, computed, makeObservable, runInAction } from "mobx";
|
||||
import { action, computed, makeObservable, observable, runInAction } from "mobx";
|
||||
import set from "lodash/set";
|
||||
// services
|
||||
import { IssueLabelService } from "services/issue";
|
||||
@ -10,9 +10,13 @@ import { IIssueLabel, IIssueLabelTree } from "@plane/types";
|
||||
import { ILabelRootStore } from "store/label";
|
||||
|
||||
export interface IProjectLabelStore {
|
||||
//Loaders
|
||||
fetchedMap: Record<string, boolean>;
|
||||
// computed
|
||||
projectLabels: IIssueLabel[] | undefined;
|
||||
projectLabelsTree: IIssueLabelTree[] | undefined;
|
||||
//computed actions
|
||||
getProjectLabels: (projectId: string) => IIssueLabel[] | undefined;
|
||||
// fetch actions
|
||||
fetchProjectLabels: (workspaceSlug: string, projectId: string) => Promise<IIssueLabel[]>;
|
||||
// crud actions
|
||||
@ -40,15 +44,21 @@ export class ProjectLabelStore implements IProjectLabelStore {
|
||||
rootStore;
|
||||
// root store labelMap
|
||||
labelMap: Record<string, IIssueLabel> = {};
|
||||
//loaders
|
||||
fetchedMap: Record<string, boolean> = {};
|
||||
// services
|
||||
issueLabelService;
|
||||
|
||||
constructor(_labelRoot: ILabelRootStore, _rootStore: RootStore) {
|
||||
makeObservable(this, {
|
||||
labelMap: observable,
|
||||
fetchedMap: observable,
|
||||
// computed
|
||||
projectLabels: computed,
|
||||
projectLabelsTree: computed,
|
||||
// actions
|
||||
getProjectLabels: action,
|
||||
|
||||
fetchProjectLabels: action,
|
||||
createLabel: action,
|
||||
updateLabel: action,
|
||||
@ -68,7 +78,7 @@ export class ProjectLabelStore implements IProjectLabelStore {
|
||||
*/
|
||||
get projectLabels() {
|
||||
const projectId = this.rootStore.app.router.projectId;
|
||||
if (!projectId || !this.labelMap) return;
|
||||
if (!projectId || !this.fetchedMap[projectId] || !this.labelMap) return;
|
||||
return Object.values(this.labelMap ?? {}).filter((label) => label.project === projectId);
|
||||
}
|
||||
|
||||
@ -80,6 +90,11 @@ export class ProjectLabelStore implements IProjectLabelStore {
|
||||
return buildTree(this.projectLabels);
|
||||
}
|
||||
|
||||
getProjectLabels = (projectId: string) => {
|
||||
if (!this.fetchedMap[projectId] || !this.labelMap) return;
|
||||
return Object.values(this.labelMap ?? {}).filter((label) => label.project === projectId);
|
||||
};
|
||||
|
||||
/**
|
||||
* Fetches all the labelMap belongs to a specific project
|
||||
* @param workspaceSlug
|
||||
@ -92,6 +107,7 @@ export class ProjectLabelStore implements IProjectLabelStore {
|
||||
response.forEach((label) => {
|
||||
set(this.labelMap, [label.id], label);
|
||||
});
|
||||
set(this.fetchedMap, projectId, true);
|
||||
});
|
||||
return response;
|
||||
});
|
||||
|
@ -120,7 +120,8 @@ export class ProjectMemberStore implements IProjectMemberStore {
|
||||
* @param projectId
|
||||
*/
|
||||
getProjectMemberIds = (projectId: string): string[] | null => {
|
||||
let members = Object.values(this.projectMemberMap?.[projectId] ?? {});
|
||||
if (!this.projectMemberMap?.[projectId]) return null;
|
||||
let members = Object.values(this.projectMemberMap?.[projectId]);
|
||||
members = sortBy(members, [
|
||||
(m) => m.member !== this.userStore.currentUser?.id,
|
||||
(m) => this.memberRoot?.memberMap?.[m.member]?.display_name?.toLowerCase(),
|
||||
|
@ -9,6 +9,8 @@ import { IModule, ILinkDetails } from "@plane/types";
|
||||
import { RootStore } from "store/root.store";
|
||||
|
||||
export interface IModuleStore {
|
||||
//Loaders
|
||||
fetchedMap: Record<string, boolean>;
|
||||
// observables
|
||||
moduleMap: Record<string, IModule>;
|
||||
// computed
|
||||
@ -51,6 +53,8 @@ export interface IModuleStore {
|
||||
export class ModulesStore implements IModuleStore {
|
||||
// observables
|
||||
moduleMap: Record<string, IModule> = {};
|
||||
//loaders
|
||||
fetchedMap: Record<string, boolean> = {};
|
||||
// root store
|
||||
rootStore;
|
||||
// services
|
||||
@ -61,6 +65,7 @@ export class ModulesStore implements IModuleStore {
|
||||
makeObservable(this, {
|
||||
// observables
|
||||
moduleMap: observable,
|
||||
fetchedMap: observable,
|
||||
// computed
|
||||
projectModuleIds: computed,
|
||||
// computed actions
|
||||
@ -92,7 +97,7 @@ export class ModulesStore implements IModuleStore {
|
||||
*/
|
||||
get projectModuleIds() {
|
||||
const projectId = this.rootStore.app.router.projectId;
|
||||
if (!projectId) return null;
|
||||
if (!projectId || !this.fetchedMap[projectId]) return null;
|
||||
let projectModules = Object.values(this.moduleMap).filter((m) => m.project === projectId);
|
||||
projectModules = sortBy(projectModules, [(m) => !m.is_favorite, (m) => m.name.toLowerCase()]);
|
||||
const projectModuleIds = projectModules.map((m) => m.id);
|
||||
@ -111,10 +116,12 @@ export class ModulesStore implements IModuleStore {
|
||||
* @param projectId
|
||||
*/
|
||||
getProjectModuleIds = (projectId: string) => {
|
||||
if (!this.fetchedMap[projectId]) return null;
|
||||
|
||||
let projectModules = Object.values(this.moduleMap).filter((m) => m.project === projectId);
|
||||
projectModules = sortBy(projectModules, [(m) => !m.is_favorite, (m) => m.name.toLowerCase()]);
|
||||
const projectModuleIds = projectModules.map((m) => m.id);
|
||||
return projectModuleIds || null;
|
||||
return projectModuleIds;
|
||||
};
|
||||
|
||||
/**
|
||||
@ -129,6 +136,7 @@ export class ModulesStore implements IModuleStore {
|
||||
response.forEach((module) => {
|
||||
set(this.moduleMap, [module.id], { ...this.moduleMap[module.id], ...module });
|
||||
});
|
||||
set(this.fetchedMap, projectId, true);
|
||||
});
|
||||
return response;
|
||||
});
|
||||
|
@ -7,6 +7,8 @@ import { RootStore } from "store/root.store";
|
||||
import { IProjectView } from "@plane/types";
|
||||
|
||||
export interface IProjectViewStore {
|
||||
//Loaders
|
||||
fetchedMap: Record<string, boolean>;
|
||||
// observables
|
||||
viewMap: Record<string, IProjectView>;
|
||||
// computed
|
||||
@ -33,6 +35,8 @@ export interface IProjectViewStore {
|
||||
export class ProjectViewStore implements IProjectViewStore {
|
||||
// observables
|
||||
viewMap: Record<string, IProjectView> = {};
|
||||
//loaders
|
||||
fetchedMap: Record<string, boolean> = {};
|
||||
// root store
|
||||
rootStore;
|
||||
// services
|
||||
@ -42,6 +46,7 @@ export class ProjectViewStore implements IProjectViewStore {
|
||||
makeObservable(this, {
|
||||
// observables
|
||||
viewMap: observable,
|
||||
fetchedMap: observable,
|
||||
// computed
|
||||
projectViewIds: computed,
|
||||
// computed actions
|
||||
@ -68,7 +73,7 @@ export class ProjectViewStore implements IProjectViewStore {
|
||||
*/
|
||||
get projectViewIds() {
|
||||
const projectId = this.rootStore.app.router.projectId;
|
||||
if (!projectId) return null;
|
||||
if (!projectId || !this.fetchedMap[projectId]) return null;
|
||||
const viewIds = Object.keys(this.viewMap ?? {})?.filter((viewId) => this.viewMap?.[viewId]?.project === projectId);
|
||||
return viewIds;
|
||||
}
|
||||
@ -90,6 +95,7 @@ export class ProjectViewStore implements IProjectViewStore {
|
||||
response.forEach((view) => {
|
||||
set(this.viewMap, [view.id], view);
|
||||
});
|
||||
set(this.fetchedMap, projectId, true);
|
||||
});
|
||||
return response;
|
||||
});
|
||||
|
@ -9,6 +9,8 @@ import { IState } from "@plane/types";
|
||||
import { ProjectStateService } from "services/project";
|
||||
|
||||
export interface IStateStore {
|
||||
//Loaders
|
||||
fetchedMap: Record<string, boolean>;
|
||||
// observables
|
||||
stateMap: Record<string, IState>;
|
||||
// computed
|
||||
@ -16,7 +18,7 @@ export interface IStateStore {
|
||||
groupedProjectStates: Record<string, IState[]> | undefined;
|
||||
// computed actions
|
||||
getStateById: (stateId: string) => IState | undefined;
|
||||
getProjectStates: (projectId: string) => IState[];
|
||||
getProjectStates: (projectId: string) => IState[] | undefined;
|
||||
// fetch actions
|
||||
fetchProjectStates: (workspaceSlug: string, projectId: string) => Promise<IState[]>;
|
||||
// crud actions
|
||||
@ -40,6 +42,8 @@ export interface IStateStore {
|
||||
|
||||
export class StateStore implements IStateStore {
|
||||
stateMap: Record<string, IState> = {};
|
||||
//loaders
|
||||
fetchedMap: Record<string, boolean> = {};
|
||||
router;
|
||||
stateService;
|
||||
|
||||
@ -47,6 +51,7 @@ export class StateStore implements IStateStore {
|
||||
makeObservable(this, {
|
||||
// observables
|
||||
stateMap: observable,
|
||||
fetchedMap: observable,
|
||||
// computed
|
||||
projectStates: computed,
|
||||
groupedProjectStates: computed,
|
||||
@ -71,7 +76,8 @@ export class StateStore implements IStateStore {
|
||||
* Returns the stateMap belongs to a specific project
|
||||
*/
|
||||
get projectStates() {
|
||||
if (!this.router.query?.projectId) return;
|
||||
const projectId = this.router.query?.projectId?.toString();
|
||||
if (!projectId || !this.fetchedMap[projectId]) return;
|
||||
return Object.values(this.stateMap).filter((state) => state.project === this.router.query.projectId);
|
||||
}
|
||||
|
||||
@ -97,7 +103,10 @@ export class StateStore implements IStateStore {
|
||||
* @param projectId
|
||||
* @returns IState[]
|
||||
*/
|
||||
getProjectStates = (projectId: string) => Object.values(this.stateMap).filter((state) => state.project === projectId);
|
||||
getProjectStates = (projectId: string) => {
|
||||
if (!projectId || !this.fetchedMap[projectId]) return;
|
||||
return Object.values(this.stateMap).filter((state) => state.project === projectId);
|
||||
};
|
||||
|
||||
/**
|
||||
* fetches the stateMap of a project
|
||||
@ -111,6 +120,7 @@ export class StateStore implements IStateStore {
|
||||
statesResponse.forEach((state) => {
|
||||
set(this.stateMap, [state.id], state);
|
||||
});
|
||||
set(this.fetchedMap, projectId, true);
|
||||
});
|
||||
return statesResponse;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user