mirror of
https://github.com/cssgunc/compass.git
synced 2025-04-03 03:20:17 -04:00
Connect update to backend (#55)
This commit is contained in:
parent
0daf80d222
commit
55a03ff3fd
|
@ -25,9 +25,9 @@ def get_all(uuid: str, user_svc: UserService = Depends()):
|
|||
return user_svc.all()
|
||||
|
||||
|
||||
@api.get("/{user_id}", response_model=User, tags=["Users"])
|
||||
def get_by_uuid(user_id: str, user_svc: UserService = Depends()):
|
||||
return user_svc.get_user_by_uuid(user_id)
|
||||
@api.get("/{uuid}", response_model=User, tags=["Users"])
|
||||
def get_by_uuid(uuid: str, user_svc: UserService = Depends()):
|
||||
return user_svc.get_user_by_uuid(uuid)
|
||||
|
||||
|
||||
@api.post("/", response_model=User, tags=["Users"])
|
||||
|
@ -37,3 +37,12 @@ def create_user(uuid: str, user: User, user_svc: UserService = Depends()):
|
|||
raise Exception(f"Insufficient permissions for user {subject.uuid}")
|
||||
|
||||
return user_svc.create(user)
|
||||
|
||||
|
||||
@api.put("/", response_model=User, tags=["Users"])
|
||||
def update_user(uuid: str, user: User, user_svc: UserService = Depends()):
|
||||
subject = user_svc.get_user_by_uuid(uuid)
|
||||
if subject.role != UserTypeEnum.ADMIN:
|
||||
raise Exception(f"Insufficient permissions for user {subject.uuid}")
|
||||
|
||||
return user_svc.update(user)
|
||||
|
|
|
@ -17,7 +17,7 @@ class ResourceService:
|
|||
def get_resource_by_user(self, subject: User) -> list[Resource]:
|
||||
"""Resource method getting all of the resources that a user has access to based on role"""
|
||||
if subject.role != UserTypeEnum.VOLUNTEER:
|
||||
query = select(ResourceEntity)
|
||||
query = select(ResourceEntity).order_by(ResourceEntity.id)
|
||||
entities = self._session.scalars(query).all()
|
||||
return [resource.to_model() for resource in entities]
|
||||
else:
|
||||
|
@ -86,10 +86,10 @@ class ResourceService:
|
|||
raise ResourceNotFoundException(
|
||||
f"No resource found with matching id: {resource.id}"
|
||||
)
|
||||
entity.name = resource.name
|
||||
entity.summary = resource.summary
|
||||
entity.link = resource.link
|
||||
entity.program = resource.program
|
||||
entity.name = resource.name if resource.name else entity.name
|
||||
entity.summary = resource.summary if resource.summary else entity.summary
|
||||
entity.link = resource.link if resource.link else entity.link
|
||||
entity.program = resource.program if resource.program else entity.program
|
||||
self._session.commit()
|
||||
return entity.to_model()
|
||||
|
||||
|
|
|
@ -22,14 +22,18 @@ class ServiceService:
|
|||
def get_service_by_user(self, subject: User) -> list[Service]:
|
||||
"""Resource method getting all of the resources that a user has access to based on role"""
|
||||
if subject.role != UserTypeEnum.VOLUNTEER:
|
||||
query = select(ServiceEntity)
|
||||
query = select(ServiceEntity).order_by(ServiceEntity.id)
|
||||
entities = self._session.scalars(query).all()
|
||||
return [service.to_model() for service in entities]
|
||||
else:
|
||||
programs = subject.program
|
||||
services = []
|
||||
for program in programs:
|
||||
entities = self._session.query(ServiceEntity).where(ServiceEntity.program == program).all()
|
||||
entities = (
|
||||
self._session.query(ServiceEntity)
|
||||
.where(ServiceEntity.program == program)
|
||||
.all()
|
||||
)
|
||||
for entity in entities:
|
||||
services.append(entity.to_model())
|
||||
return [service for service in services]
|
||||
|
@ -37,14 +41,14 @@ class ServiceService:
|
|||
def get_service_by_name(self, name: str, subject: User) -> Service:
|
||||
"""Service method getting services by id."""
|
||||
query = select(ServiceEntity).where(
|
||||
and_(
|
||||
ServiceEntity.name == name, ServiceEntity.program.in_(subject.program)
|
||||
)
|
||||
and_(ServiceEntity.name == name, ServiceEntity.program.in_(subject.program))
|
||||
)
|
||||
entity = self._session.scalars(query).one_or_none()
|
||||
|
||||
if entity is None:
|
||||
raise ServiceNotFoundException(f"Service with name: {name} does not exist or program has not been assigned")
|
||||
raise ServiceNotFoundException(
|
||||
f"Service with name: {name} does not exist or program has not been assigned"
|
||||
)
|
||||
|
||||
return entity.to_model()
|
||||
|
||||
|
@ -66,7 +70,7 @@ class ServiceService:
|
|||
raise ProgramNotAssignedException(
|
||||
f"User is not {UserTypeEnum.ADMIN}, cannot update service"
|
||||
)
|
||||
|
||||
|
||||
query = select(ServiceEntity).where(ServiceEntity.id == service.id)
|
||||
entity = self._session.scalars(query).one_or_none()
|
||||
|
||||
|
@ -75,11 +79,13 @@ class ServiceService:
|
|||
"The service you are searching for does not exist."
|
||||
)
|
||||
|
||||
entity.name = service.name
|
||||
entity.status = service.status
|
||||
entity.summary = service.summary
|
||||
entity.requirements = service.requirements
|
||||
entity.program = service.program
|
||||
entity.name = service.name if service.name else entity.name
|
||||
entity.status = service.status if service.status else entity.status
|
||||
entity.summary = service.summary if service.summary else entity.summary
|
||||
entity.requirements = (
|
||||
service.requirements if service.requirements else entity.requirements
|
||||
)
|
||||
entity.program = service.program if service.program else entity.program
|
||||
self._session.commit()
|
||||
|
||||
return entity.to_model()
|
||||
|
@ -88,7 +94,7 @@ class ServiceService:
|
|||
"""Deletes a service from the table."""
|
||||
if subject.role != UserTypeEnum.ADMIN:
|
||||
raise ProgramNotAssignedException(f"User is not {UserTypeEnum.ADMIN}")
|
||||
|
||||
|
||||
query = select(ServiceEntity).where(ServiceEntity.id == service.id)
|
||||
entity = self._session.scalars(query).one_or_none()
|
||||
|
||||
|
|
|
@ -43,10 +43,10 @@ class UserService:
|
|||
|
||||
def all(self) -> list[User]:
|
||||
"""
|
||||
Returns a list of all Users
|
||||
Returns a list of all Users ordered by id
|
||||
|
||||
"""
|
||||
query = select(UserEntity)
|
||||
query = select(UserEntity).order_by(UserEntity.id)
|
||||
entities = self._session.scalars(query).all()
|
||||
|
||||
return [entity.to_model() for entity in entities]
|
||||
|
@ -102,12 +102,12 @@ class UserService:
|
|||
if obj is None:
|
||||
raise Exception(f"No matching user found")
|
||||
|
||||
obj.username = user.username
|
||||
obj.role = user.role
|
||||
obj.email = user.email
|
||||
obj.program = user.program
|
||||
obj.experience = user.experience
|
||||
obj.group = user.group
|
||||
obj.username = user.username if user.username else obj.username
|
||||
obj.role = user.role if user.role else obj.role
|
||||
obj.email = user.email if user.email else obj.email
|
||||
obj.program = user.program if user.program else obj.program
|
||||
obj.experience = user.experience if user.experience else obj.experience
|
||||
obj.group = user.group if user.group else obj.group
|
||||
|
||||
self._session.commit()
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { NextResponse } from "next/server";
|
||||
import User from "@/utils/models/User";
|
||||
import Resource from "@/utils/models/Resource";
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const apiEndpoint = `${process.env.NEXT_PUBLIC_API_HOST}/api/resource`;
|
||||
|
@ -25,7 +25,7 @@ export async function POST(request: Request) {
|
|||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const createdResource: User = await response.json();
|
||||
const createdResource: Resource = await response.json();
|
||||
return NextResponse.json(createdResource, { status: response.status });
|
||||
} catch (error) {
|
||||
console.error("Error creating resource:", error);
|
||||
|
|
37
compass/app/api/resource/update/route.ts
Normal file
37
compass/app/api/resource/update/route.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
import Resource from "@/utils/models/Resource";
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const apiEndpoint = `${process.env.NEXT_PUBLIC_API_HOST}/api/resource`;
|
||||
|
||||
try {
|
||||
const resourceData = await request.json();
|
||||
|
||||
console.log("RESOURCE DATA", JSON.stringify(resourceData));
|
||||
|
||||
const { searchParams } = new URL(request.url);
|
||||
const uuid = searchParams.get("uuid");
|
||||
|
||||
// Send the POST request to the backend
|
||||
const response = await fetch(`${apiEndpoint}?uuid=${uuid}`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(resourceData),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const resource: Resource = await response.json();
|
||||
return NextResponse.json(resource, { status: response.status });
|
||||
} catch (error) {
|
||||
console.error("Error creating user:", error);
|
||||
return NextResponse.json(
|
||||
{ error: "Failed to update resource" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
37
compass/app/api/service/update/route.ts
Normal file
37
compass/app/api/service/update/route.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
import { NextResponse } from "next/server";
|
||||
import Service from "@/utils/models/Service";
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const apiEndpoint = `${process.env.NEXT_PUBLIC_API_HOST}/api/service`;
|
||||
|
||||
try {
|
||||
const serviceData = await request.json();
|
||||
|
||||
console.log("SERVICE DATA", JSON.stringify(serviceData));
|
||||
|
||||
const { searchParams } = new URL(request.url);
|
||||
const uuid = searchParams.get("uuid");
|
||||
|
||||
// Send the POST request to the backend
|
||||
const response = await fetch(`${apiEndpoint}?uuid=${uuid}`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(serviceData),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const createdService: Service = await response.json();
|
||||
return NextResponse.json(createdService, { status: response.status });
|
||||
} catch (error) {
|
||||
console.error("Error creating service:", error);
|
||||
return NextResponse.json(
|
||||
{ error: "Failed to update service" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
37
compass/app/api/user/update/route.ts
Normal file
37
compass/app/api/user/update/route.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
import { NextResponse } from "next/server";
|
||||
import User from "@/utils/models/User";
|
||||
|
||||
export async function POST(request: Request) {
|
||||
const apiEndpoint = `${process.env.NEXT_PUBLIC_API_HOST}/api/user`;
|
||||
|
||||
try {
|
||||
const userData = await request.json();
|
||||
|
||||
console.log("USER DATA", JSON.stringify(userData));
|
||||
|
||||
const { searchParams } = new URL(request.url);
|
||||
const uuid = searchParams.get("uuid");
|
||||
|
||||
// Send the POST request to the backend
|
||||
const response = await fetch(`${apiEndpoint}?uuid=${uuid}`, {
|
||||
method: "PUT",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(userData),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
const createdUser: User = await response.json();
|
||||
return NextResponse.json(createdUser, { status: response.status });
|
||||
} catch (error) {
|
||||
console.error("Error creating user:", error);
|
||||
return NextResponse.json(
|
||||
{ error: "Failed to create user" },
|
||||
{ status: 500 }
|
||||
);
|
||||
}
|
||||
}
|
|
@ -32,6 +32,7 @@ type DrawerProps = {
|
|||
rowContent?: any;
|
||||
setRowContent?: Dispatch<SetStateAction<any>>;
|
||||
isAdmin?: boolean;
|
||||
updateRoute: string;
|
||||
};
|
||||
|
||||
const Drawer: FunctionComponent<DrawerProps> = ({
|
||||
|
@ -40,6 +41,7 @@ const Drawer: FunctionComponent<DrawerProps> = ({
|
|||
rowContent,
|
||||
setRowContent,
|
||||
isAdmin,
|
||||
updateRoute,
|
||||
}: DrawerProps) => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const [isFull, setIsFull] = useState(false);
|
||||
|
@ -67,18 +69,39 @@ const Drawer: FunctionComponent<DrawerProps> = ({
|
|||
}
|
||||
};
|
||||
|
||||
const handleUpdate = async () => {
|
||||
const response = await fetch(updateRoute, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(tempRowContent),
|
||||
});
|
||||
|
||||
return response;
|
||||
};
|
||||
|
||||
const toggleDrawer = () => {
|
||||
if (setRowContent && isOpen) {
|
||||
setRowContent((prev: any) => {
|
||||
return prev.map((row: any) => {
|
||||
if (row.id === tempRowContent.id) {
|
||||
return tempRowContent;
|
||||
}
|
||||
return row;
|
||||
});
|
||||
});
|
||||
// Check if any values have changed
|
||||
const hasChanges = Object.keys(tempRowContent).some(
|
||||
(key) => tempRowContent[key] !== rowContent[key]
|
||||
);
|
||||
|
||||
console.log("Send API request to update row content");
|
||||
if (hasChanges) {
|
||||
handleUpdate().then((response) => {
|
||||
if (response.ok) {
|
||||
setRowContent((prev: any) => {
|
||||
return prev.map((row: any) => {
|
||||
if (row.id === tempRowContent.id) {
|
||||
return tempRowContent;
|
||||
}
|
||||
return row;
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
setIsOpen(!isOpen);
|
||||
|
|
|
@ -81,6 +81,7 @@ export default function ResourceTable({
|
|||
setData={setData}
|
||||
details={resourceDetails}
|
||||
isAdmin={user?.role === "ADMIN"}
|
||||
updateRoute={`/api/resource/update?uuid=${user?.uuid}`}
|
||||
/>
|
||||
),
|
||||
}),
|
||||
|
|
|
@ -14,6 +14,7 @@ type RowOpenActionProps<T extends DataPoint> = {
|
|||
setData: Dispatch<SetStateAction<T[]>>;
|
||||
details: Details[];
|
||||
isAdmin?: boolean;
|
||||
updateRoute: string;
|
||||
};
|
||||
|
||||
export function RowOpenAction<T extends DataPoint>({
|
||||
|
@ -23,6 +24,7 @@ export function RowOpenAction<T extends DataPoint>({
|
|||
setData,
|
||||
details,
|
||||
isAdmin,
|
||||
updateRoute,
|
||||
}: RowOpenActionProps<T>) {
|
||||
return (
|
||||
<div className="font-semibold group flex flex-row items-center justify-between pr-2">
|
||||
|
@ -34,6 +36,7 @@ export function RowOpenAction<T extends DataPoint>({
|
|||
details={details}
|
||||
setRowContent={setData}
|
||||
isAdmin={isAdmin}
|
||||
updateRoute={updateRoute}
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
|
|
|
@ -39,13 +39,34 @@ export default function ServiceTable({
|
|||
]);
|
||||
|
||||
const [requirementPresets, setRequirementPresets] = useState([
|
||||
"ANONYMOUS",
|
||||
"CONFIDENTIAL",
|
||||
"REFERRAL REQUIRED",
|
||||
"SAFETY ASSESSMENT",
|
||||
"INTAKE REQUIRED",
|
||||
"INCOME ELIGIBILITY",
|
||||
"INITIAL ASSESSMENT",
|
||||
"Anonymous",
|
||||
"Confidential",
|
||||
"Referral required",
|
||||
"Safety assessment",
|
||||
"Intake required",
|
||||
"Income eligibility",
|
||||
"Initial assessment",
|
||||
"Insurance accepted",
|
||||
"Open to parents",
|
||||
"18+",
|
||||
"Application required",
|
||||
"Proof of income",
|
||||
"Background check",
|
||||
"Enrollment required",
|
||||
"Registration required",
|
||||
"Parental consent",
|
||||
"Age-appropriate",
|
||||
"Collaborative",
|
||||
"Open to the public",
|
||||
"Registration preferred",
|
||||
"Legal case",
|
||||
"Scheduling required",
|
||||
"Limited availability",
|
||||
"Eligibility assessment",
|
||||
"Pre-registration required",
|
||||
"Commitment to attend",
|
||||
"Training required",
|
||||
"Based on individual needs",
|
||||
]);
|
||||
|
||||
const serviceDetails: Details[] = [
|
||||
|
@ -100,6 +121,8 @@ export default function ServiceTable({
|
|||
rowData={info.row.original}
|
||||
setData={setData}
|
||||
details={serviceDetails}
|
||||
updateRoute={`/api/service/update?uuid=${user?.uuid}`}
|
||||
isAdmin={user?.role === "ADMIN"}
|
||||
/>
|
||||
),
|
||||
}),
|
||||
|
@ -137,7 +160,7 @@ export default function ServiceTable({
|
|||
</>
|
||||
),
|
||||
cell: (info) => (
|
||||
<div className="flex flex-wrap gap-2 items-center px-2">
|
||||
<div className="flex flex-wrap gap-2 items-center p-2">
|
||||
{info.getValue().length > 0 ? (
|
||||
info.getValue().map((tag: string, index: number) => {
|
||||
return <Tag key={index}>{tag}</Tag>;
|
||||
|
|
|
@ -81,6 +81,8 @@ export default function Table<T extends DataPoint>({
|
|||
createEndpoint,
|
||||
isAdmin = false,
|
||||
}: TableProps<T>) {
|
||||
console.log(data);
|
||||
|
||||
const columnHelper = createColumnHelper<T>();
|
||||
|
||||
const createRow = async (newItem: any) => {
|
||||
|
|
|
@ -84,6 +84,7 @@ export default function UserTable({ data, setData, user }: UserTableProps) {
|
|||
setData={setData}
|
||||
details={userDetails}
|
||||
isAdmin={user?.role === "ADMIN"}
|
||||
updateRoute={`/api/user/update?uuid=${user?.uuid}`}
|
||||
/>
|
||||
),
|
||||
}),
|
||||
|
@ -122,7 +123,7 @@ export default function UserTable({ data, setData, user }: UserTableProps) {
|
|||
</>
|
||||
),
|
||||
cell: (info) => (
|
||||
<div className="flex ml-2 flex-wrap gap-2 items-center">
|
||||
<div className="flex p-2 flex-wrap gap-2 items-center">
|
||||
{info.getValue().length > 0 ? (
|
||||
info.getValue().map((tag: string, index: number) => {
|
||||
return <Tag key={index}>{tag}</Tag>;
|
||||
|
|
|
@ -15,7 +15,7 @@ export const TagDropdown = ({
|
|||
handleAdd,
|
||||
}: TagDropdownProps) => {
|
||||
return (
|
||||
<div className="z-50 flex flex-col space-y-2 mt-2">
|
||||
<div className="z-50 flex flex-col space-y-2 mt-2 max-h-60 overflow-y-auto scrollbar-thin scrollbar-track-gray-100 scrollbar-thumb-gray-300 pr-2">
|
||||
{Array.from(tags).length > 0 ? (
|
||||
Array.from(tags).map((tag, index) => (
|
||||
<div
|
||||
|
|
|
@ -21,7 +21,7 @@ const config: Config = {
|
|||
},
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
plugins: [require("tailwind-scrollbar")],
|
||||
};
|
||||
|
||||
export default config;
|
||||
|
|
26
package-lock.json
generated
26
package-lock.json
generated
|
@ -1,12 +1,13 @@
|
|||
{
|
||||
"name": "compass",
|
||||
"name": "workspace",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"devDependencies": {
|
||||
"husky": "^9.0.11",
|
||||
"lint-staged": "^15.2.2"
|
||||
"lint-staged": "^15.2.2",
|
||||
"tailwind-scrollbar": "^4.0.0-beta.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-escapes": {
|
||||
|
@ -649,6 +650,27 @@
|
|||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwind-scrollbar": {
|
||||
"version": "4.0.0-beta.0",
|
||||
"resolved": "https://registry.npmjs.org/tailwind-scrollbar/-/tailwind-scrollbar-4.0.0-beta.0.tgz",
|
||||
"integrity": "sha512-d6qwt3rYDgsKNaQGLW0P6N1TN/87xYZDjH6/PimtFvij2NgC5i3M6mEuVKR4Ixb2u3SvMBT95t7+xzJGJRzXtA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12.13.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tailwindcss": "^4.0.0-beta.8"
|
||||
}
|
||||
},
|
||||
"node_modules/tailwindcss": {
|
||||
"version": "4.0.0-beta.8",
|
||||
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.0-beta.8.tgz",
|
||||
"integrity": "sha512-21HmdRq9tHDLJZavb2cRBGJxBvRODpwb0/t3tRbMOl65hJE6zG6K6lD6lLS3IOC35u4SOjKjdZiJJi9AuWCf+Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
|
|
33
package.json
33
package.json
|
@ -1,21 +1,22 @@
|
|||
{
|
||||
"devDependencies": {
|
||||
"husky": "^9.0.11",
|
||||
"lint-staged": "^15.2.2"
|
||||
"lint-staged": "^15.2.2",
|
||||
"tailwind-scrollbar": "^4.0.0-beta.0"
|
||||
},
|
||||
"scripts": {
|
||||
"precommit": "lint-staged"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.ts": [
|
||||
"cd compass",
|
||||
"prettier --write",
|
||||
"git add"
|
||||
],
|
||||
"*.tsx": [
|
||||
"cd compass",
|
||||
"prettier --write",
|
||||
"git add"
|
||||
]
|
||||
}
|
||||
"scripts": {
|
||||
"precommit": "lint-staged"
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.ts": [
|
||||
"cd compass",
|
||||
"prettier --write",
|
||||
"git add"
|
||||
],
|
||||
"*.tsx": [
|
||||
"cd compass",
|
||||
"prettier --write",
|
||||
"git add"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user