docs: updates to admin customization docs (#7493)
* updated admin docs * re-add navigation and parameters sections * update injection zones * update cli scripts docs * added list of injection zones * add details about widget props * restructure admin injection zones
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -4,19 +4,12 @@ export const metadata = {
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
<Note type="soon">
|
||||
|
||||
Admin customizations are coming soon.
|
||||
|
||||
</Note>
|
||||
|
||||
In the next chapters, you'll learn more about possible admin customizations.
|
||||
|
||||
You can customize the admin dashboard by:
|
||||
|
||||
- Adding new sections to existing pages using Widgets.
|
||||
- Adding new pages using UI Routes.
|
||||
- Adding new pages to the Settings section of the admin dashboard using Setting Pages.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,152 +0,0 @@
|
||||
export const metadata = {
|
||||
title: `${pageNumber} Admin Setting Pages`,
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
<Note type="soon">
|
||||
|
||||
Admin customizations are coming soon.
|
||||
|
||||
</Note>
|
||||
|
||||
In this chapter, you’ll learn how to create a setting page in the admin dashboard.
|
||||
|
||||
## What is a Setting Page?
|
||||
|
||||
A setting page is a React component that adds a new page to the settings panel of the admin dashboard.
|
||||
|
||||
For example, you may create a setting page to manage configurations related to your custom functionalities.
|
||||
|
||||
---
|
||||
|
||||
## How to Create a Setting Page?
|
||||
|
||||
A setting page is created in a file named `page.tsx` under the `src/admin/settings` directory. The file’s default export must be the React component, and it must also export a configuration object.
|
||||
|
||||
For example, you can create the file `src/admin/settings/custom/page.tsx` with the following content:
|
||||
|
||||
```tsx title="src/admin/settings/custom/page.tsx"
|
||||
import type { SettingConfig } from "@medusajs/admin"
|
||||
import { ChatBubbleLeftRight } from "@medusajs/icons"
|
||||
|
||||
const CustomSettingPage = () => {
|
||||
return (
|
||||
<div>
|
||||
<h1>Custom Setting Page</h1>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const config: SettingConfig = {
|
||||
card: {
|
||||
label: "Custom",
|
||||
description: "Manage your custom settings",
|
||||
// optional
|
||||
icon: ChatBubbleLeftRight,
|
||||
},
|
||||
}
|
||||
|
||||
export default CustomSettingPage
|
||||
```
|
||||
|
||||
The new setting page’s path is the file’s path relative to `src/admin/settings` and prefixed with `/a/settings`. So, the above UI route is a new page added at the path `localhost:7001/a/settings/custom`.
|
||||
|
||||
The setting page is shown on the Settings panel as a card. In the exported configuration object, you configure the card’s label, description, and optionally icon. In this example, you use an icon from [Medusa’s UI Icons package](https://docs.medusajs.com/ui/icons/overview).
|
||||
|
||||
### Test the Setting Page
|
||||
|
||||
To test the setting page, start the Medusa application:
|
||||
|
||||
```bash npm2yarn
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Then, open the Settings page of the admin dashboard. You’ll find a new card that links to your setting page.
|
||||
|
||||
---
|
||||
|
||||
## Using UI Components
|
||||
|
||||
Similar to other admin customizations, it’s highly recommended that you use the [Medusa UI package](https://docs.medusajs.com/ui) to match your page’s design with the rest of the Medusa Admin.
|
||||
|
||||
For example, you can rewrite the above UI route to the following:
|
||||
|
||||
```tsx title="src/admin/settings/custom/page.tsx"
|
||||
import type { SettingConfig } from "@medusajs/admin"
|
||||
import { ChatBubbleLeftRight } from "@medusajs/icons"
|
||||
import { Container, Heading } from "@medusajs/ui"
|
||||
|
||||
const CustomSettingPage = () => {
|
||||
return (
|
||||
<Container>
|
||||
<Heading level="h1">Custom Setting Page</Heading>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
export const config: SettingConfig = {
|
||||
card: {
|
||||
label: "Custom",
|
||||
description: "Manage your custom settings",
|
||||
icon: ChatBubbleLeftRight,
|
||||
},
|
||||
}
|
||||
|
||||
export default CustomSettingPage
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Setting Page Props
|
||||
|
||||
A setting page receives a `notify` prop, which is an object having the following properties:
|
||||
|
||||
- `success`: a function that shows a success toast message.
|
||||
- `error`: a function that shows an error toast message.
|
||||
- `warn`: a function that shows a warning toast message.
|
||||
- `info`: a function that shows an info toast message.
|
||||
|
||||
Each of these functions accepts two parameters: the message’s title and the message’s content.
|
||||
|
||||
For example:
|
||||
|
||||
```tsx title="src/admin/settings/custom/page.tsx" highlights={[["11", "success", "Show a success toast message on the click of a button."]]}
|
||||
import type { RouteProps, SettingConfig } from "@medusajs/admin"
|
||||
import { ChatBubbleLeftRight } from "@medusajs/icons"
|
||||
import { Container, Heading, Button } from "@medusajs/ui"
|
||||
|
||||
const CustomSettingPage = ({ notify }: RouteProps) => {
|
||||
return (
|
||||
<Container>
|
||||
<Heading level="h1">Custom Setting Page</Heading>
|
||||
<Button
|
||||
onClick={() =>
|
||||
notify.success("Success!", "You clicked the button!")
|
||||
}
|
||||
className="mt-3"
|
||||
>
|
||||
Click me
|
||||
</Button>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
export const config: SettingConfig = {
|
||||
card: {
|
||||
label: "Custom",
|
||||
description: "Manage your custom settings",
|
||||
icon: ChatBubbleLeftRight,
|
||||
},
|
||||
}
|
||||
|
||||
export default CustomSettingPage
|
||||
```
|
||||
|
||||
If you click the button on your setting page, a toast message will show with the title and message you specified.
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
Admin UI Routes support [Tailwind CSS](https://tailwindcss.com/) out of the box.
|
||||
|
||||
</Note>
|
||||
@@ -4,22 +4,8 @@ export const metadata = {
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
<Note type="soon">
|
||||
|
||||
Admin customizations are coming soon.
|
||||
|
||||
</Note>
|
||||
|
||||
In this chapter, you'll find some tips for your admin development.
|
||||
|
||||
## Sending Requests to API Routes
|
||||
|
||||
To send requests to your API routes in your admin customizations, it’s highly recommended to use the Medusa React or Medusa JS Client packages.
|
||||
|
||||
Check out their references for more details on available hooks or methods and how to use them.
|
||||
|
||||
---
|
||||
|
||||
## Routing Functionalities
|
||||
|
||||
To navigate or link to other pages, or use other routing functionalities, use the [react-router-dom](https://reactrouter.com/en/main) package:
|
||||
@@ -30,40 +16,34 @@ npm install react-router-dom
|
||||
|
||||
For example:
|
||||
|
||||
```tsx title="src/admin/widgets/product-widget.tsx" highlights={[["18", "Link", "Add a link to another page."]]}
|
||||
import type {
|
||||
WidgetConfig,
|
||||
ProductDetailsWidgetProps,
|
||||
} from "@medusajs/admin"
|
||||
import { Container, Heading, Button } from "@medusajs/ui"
|
||||
export const highlights = [
|
||||
["9", "Link", "Add a link to another page"],
|
||||
["9", '"/orders', "Add the path without the `/app` prefix."]
|
||||
]
|
||||
|
||||
```tsx title="src/admin/widgets/product-widget.tsx" highlights={highlights}
|
||||
import { defineWidgetConfig } from "@medusajs/admin-shared"
|
||||
import { Container } from "@medusajs/ui"
|
||||
import { Link } from "react-router-dom"
|
||||
|
||||
// The widget
|
||||
const ProductWidget = ({
|
||||
product,
|
||||
}: ProductDetailsWidgetProps) => {
|
||||
const ProductWidget = () => {
|
||||
return (
|
||||
<Container>
|
||||
<Heading level="h2">
|
||||
Product Widget {product.title}
|
||||
</Heading>
|
||||
<Button className="mt-3" variant="transparent">
|
||||
<Link to={"/a/orders"}>View Orders</Link>
|
||||
</Button>
|
||||
<Link to={"/orders"}>View Orders</Link>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
// The widget's configurations
|
||||
export const config: WidgetConfig = {
|
||||
export const config = defineWidgetConfig({
|
||||
zone: "product.details.after",
|
||||
}
|
||||
})
|
||||
|
||||
export default ProductWidget
|
||||
|
||||
```
|
||||
|
||||
This adds a widget in a product's details page with a link to the Orders page.
|
||||
This adds a widget in a product's details page with a link to the Orders page. The link's path must be without the `/app` prefix.
|
||||
|
||||
<Note title="Learn more">
|
||||
|
||||
|
||||
@@ -4,17 +4,11 @@ export const metadata = {
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
<Note type="soon">
|
||||
|
||||
Admin customizations are coming soon.
|
||||
|
||||
</Note>
|
||||
|
||||
In this chapter, you’ll learn how to create a UI route in the admin dashboard.
|
||||
|
||||
## What is a UI Route?
|
||||
|
||||
A UI route is a React Component that adds a custom new page to your admin dashboard. The UI Route can be shown in the sidebar or added as a nested page.
|
||||
A UI route is a React Component that adds a new page to your admin dashboard. The UI Route can be shown in the sidebar or added as a nested page.
|
||||
|
||||
For example, you may add a new page to manage product reviews.
|
||||
|
||||
@@ -34,7 +28,7 @@ const CustomPage = () => {
|
||||
export default CustomPage
|
||||
```
|
||||
|
||||
The new page’s path is the file’s path relative to `src/admin/routes` and prefixed with `/a`. So, the above UI route is a new page added at the path `localhost:7001/a/custom`.
|
||||
The new page’s path is the file’s path relative to `src/admin/routes`. So, the above UI route is a new page added at the path `localhost:9000/app/custom`.
|
||||
|
||||
### Test the UI Route
|
||||
|
||||
@@ -44,7 +38,43 @@ To test the UI route, start the Medusa application:
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Then, after logging into the admin dashboard, open the page `localhost:7001/a/custom` to see your custom page.
|
||||
Then, after logging into the admin dashboard, open the page `localhost:9000/app/custom` to see your custom page.
|
||||
|
||||
---
|
||||
|
||||
## Show UI Route in the Sidebar
|
||||
|
||||
A UI route file can export a configuration object that indicates a new item must be added in the sidebar linking to the new UI route.
|
||||
|
||||
For example:
|
||||
|
||||
export const highlights = [
|
||||
["14", "label", "The label of the UI route's sidebar item."],
|
||||
["15", "icon", "The icon of the UI route's sidebar item."]
|
||||
]
|
||||
|
||||
```tsx title="src/admin/routes/custom/page.tsx" highlights={[["21"], ["22"], ["23"], ["24"], ["25"], ["26"]]}
|
||||
import { defineRouteConfig } from "@medusajs/admin-shared";
|
||||
import { ChatBubbleLeftRight } from "@medusajs/icons"
|
||||
|
||||
const CustomPage = () => {
|
||||
return <div>This is my custom route</div>
|
||||
}
|
||||
|
||||
export const config = defineRouteConfig({
|
||||
label: "Custom Route",
|
||||
icon: ChatBubbleLeftRight,
|
||||
})
|
||||
|
||||
export default CustomPage
|
||||
```
|
||||
|
||||
The configuration object is creaetd by the `defineRouteConfig` function imported from `@medusajs/admin-shared`. It accepts the following properties:
|
||||
|
||||
- `label`: the new sidebar item’s label.
|
||||
- `icon`: an optional React component that acts as an icon in the sidebar.
|
||||
|
||||
The above example adds a new sidebar item with the label `Custom Route` and an icon from the [Medusa UI Icons package](!ui!/icons/overview).
|
||||
|
||||
---
|
||||
|
||||
@@ -55,104 +85,50 @@ Similar to Widgets, it’s highly recommended that you use the [Medusa UI packag
|
||||
For example, you can rewrite the above UI route to the following:
|
||||
|
||||
```tsx title="src/admin/routes/custom/page.tsx"
|
||||
import { defineRouteConfig } from "@medusajs/admin-shared"
|
||||
import { ChatBubbleLeftRight } from "@medusajs/icons"
|
||||
import { Container } from "@medusajs/ui"
|
||||
|
||||
const CustomPage = () => {
|
||||
return <Container>This is my custom route</Container>
|
||||
}
|
||||
|
||||
export const config = defineRouteConfig({
|
||||
label: "Custom Route",
|
||||
icon: ChatBubbleLeftRight,
|
||||
})
|
||||
|
||||
export default CustomPage
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## UI Route Props
|
||||
## Create Settings Page
|
||||
|
||||
A UI Route receives a `notify` prop, which is an object having the following properties:
|
||||
|
||||
- `success`: a function that shows a success toast message.
|
||||
- `error`: a function that shows an error toast message.
|
||||
- `warn`: a function that shows a warning toast message.
|
||||
- `info`: a function that shows an info toast message.
|
||||
|
||||
Each of these functions accepts two parameters: the message’s title and the message’s content.
|
||||
To create a page under the settings section of the admin dashboard, create the UI route file under the path `src/admin/routes/settings`.
|
||||
|
||||
For example:
|
||||
|
||||
```tsx title="src/admin/routes/custom/page.tsx" highlights={[["10", "success", "Show a success toast message on the click of a button."]]}
|
||||
import { RouteProps } from "@medusajs/admin"
|
||||
import { Container, Text, Button } from "@medusajs/ui"
|
||||
```tsx title="src/admin/routes/settings/custom/page.tsx"
|
||||
import { defineRouteConfig } from "@medusajs/admin-shared"
|
||||
import { Container, Heading } from "@medusajs/ui"
|
||||
|
||||
const CustomPage = ({ notify }: RouteProps) => {
|
||||
const CustomSettingPage = () => {
|
||||
return (
|
||||
<Container>
|
||||
<Text>This is my custom route</Text>
|
||||
<Button
|
||||
onClick={() =>
|
||||
notify.success("Success!", "You clicked the button!")
|
||||
}
|
||||
className="mt-3"
|
||||
>
|
||||
Click me
|
||||
</Button>
|
||||
<Heading level="h1">Custom Setting Page</Heading>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
export default CustomPage
|
||||
export const config = defineRouteConfig({
|
||||
label: "Custom",
|
||||
})
|
||||
|
||||
export default CustomSettingPage;
|
||||
```
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
Admin UI Routes support [Tailwind CSS](https://tailwindcss.com/) out of the box.
|
||||
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## Show UI Route in the Sidebar
|
||||
|
||||
A UI route file can export a configuration object that indicates a new item must be added in the sidebar linking to the new UI route.
|
||||
|
||||
The configuration object has the property `link`, which is an object having the following properties:
|
||||
|
||||
- `label`: the new sidebar item’s label.
|
||||
- `icon`: an optional React component that acts as an icon in the sidebar. If not provided, a default icon is used.
|
||||
|
||||
For example:
|
||||
|
||||
```tsx title="src/admin/routes/custom/page.tsx" highlights={[["21"], ["22"], ["23"], ["24"], ["25"], ["26"]]}
|
||||
import { RouteConfig, RouteProps } from "@medusajs/admin"
|
||||
import { Container, Text, Button } from "@medusajs/ui"
|
||||
import { ChatBubbleLeftRight } from "@medusajs/icons"
|
||||
|
||||
const CustomPage = ({ notify }: RouteProps) => {
|
||||
return (
|
||||
<Container>
|
||||
<Text>This is my custom route</Text>
|
||||
<Button
|
||||
onClick={() =>
|
||||
notify.success("Success!", "You clicked the button!")
|
||||
}
|
||||
className="mt-3"
|
||||
>
|
||||
Click me
|
||||
</Button>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
export const config: RouteConfig = {
|
||||
link: {
|
||||
label: "Custom Route",
|
||||
icon: ChatBubbleLeftRight,
|
||||
},
|
||||
}
|
||||
|
||||
export default CustomPage
|
||||
```
|
||||
|
||||
This adds a new sidebar item with the label `Custom Route` and an icon from the [Medusa UI Icons package](https://docs.medusajs.com/ui/icons/overview).
|
||||
This adds a page under the path `/app/settings/custom`. An item is also added to the settings sidebar with the label `Custom`.
|
||||
|
||||
---
|
||||
|
||||
@@ -179,4 +155,6 @@ const CustomPage = () => {
|
||||
}
|
||||
|
||||
export default CustomPage
|
||||
```
|
||||
```
|
||||
|
||||
If you run the Medusa application and go to `localhost:9000/app/custom/123`, you'll see `123` printed in the page.
|
||||
|
||||
@@ -6,12 +6,6 @@ export const metadata = {
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
<Note type="soon">
|
||||
|
||||
Admin customizations are coming soon.
|
||||
|
||||
</Note>
|
||||
|
||||
In this chapter, you’ll learn more about widgets and how to use them.
|
||||
|
||||
## What is an Admin Widget?
|
||||
@@ -28,8 +22,13 @@ A widget is created in a file under the `src/admin/widgets` directory. The file
|
||||
|
||||
For example, create the file `src/admin/widgets/product-widget.tsx` with the following content:
|
||||
|
||||
```tsx title="src/admin/widgets/product-widget.tsx" highlights={[["4", "ProductWidget", "The React component of the product widget."], ["14", "", "The zone to inject the widget to."]]}
|
||||
import type { WidgetConfig } from "@medusajs/admin"
|
||||
export const widgetHighlights = [
|
||||
["4", "ProductWidget", "The React component of the product widget."],
|
||||
["14", "zone", "The zone to inject the widget to."]
|
||||
]
|
||||
|
||||
```tsx title="src/admin/widgets/product-widget.tsx" highlights={widgetHighlights}
|
||||
import { defineWidgetConfig } from "@medusajs/admin-shared"
|
||||
|
||||
// The widget
|
||||
const ProductWidget = () => {
|
||||
@@ -41,16 +40,20 @@ const ProductWidget = () => {
|
||||
}
|
||||
|
||||
// The widget's configurations
|
||||
export const config: WidgetConfig = {
|
||||
export const config = defineWidgetConfig({
|
||||
zone: "product.details.after",
|
||||
}
|
||||
})
|
||||
|
||||
export default ProductWidget
|
||||
```
|
||||
|
||||
The widget only shows the heading `Product Widget`.
|
||||
|
||||
In the exported widget’s configurations, you must specify the zone to inject the widget into. The `zone` property can be a string or an array of strings, each being the name of the injection zone.
|
||||
Use the `defineWidgetConfig` function imported from `@medusajs/admin-shared` to create and export the widget's configurations.
|
||||
|
||||
The function accepts as a parameter an object with the following property:
|
||||
|
||||
- `zone`: A string or an array of strings, each being the name of the zone to inject the widget into.
|
||||
|
||||
In the example above, the widget is injected after a product’s details.
|
||||
|
||||
@@ -66,6 +69,37 @@ Then, open a product’s details page. You’ll find your custom widget at the b
|
||||
|
||||
---
|
||||
|
||||
## Detail Widget Props
|
||||
|
||||
Widgets that are injected into a details page (for example, `product.details.after`) receive a `data` prop, which is the main data of the details page (for example, the product object).
|
||||
|
||||
For example:
|
||||
|
||||
```tsx title="src/admin/widgets/product-widget.tsx" highlights={[["5"]]}
|
||||
import { defineWidgetConfig } from "@medusajs/admin-shared"
|
||||
import { DetailWidgetProps, AdminProduct } from "@medusajs/types"
|
||||
|
||||
const ProductWidget = ({
|
||||
data
|
||||
}: DetailWidgetProps<AdminProduct>) => {
|
||||
return (
|
||||
<div>
|
||||
<h2>Product Widget {data.title}</h2>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export const config = defineWidgetConfig({
|
||||
zone: "product.details.after",
|
||||
})
|
||||
|
||||
export default ProductWidget
|
||||
```
|
||||
|
||||
Notice that the type of the props is `DetailWidgetProps`, which accepts as a type argument the expected type of the data.
|
||||
|
||||
---
|
||||
|
||||
## Using UI Components
|
||||
|
||||
It’s highly recommended that you use the [Medusa UI package](https://docs.medusajs.com/ui) to match your widget’s design with the rest of the Medusa Admin.
|
||||
@@ -73,7 +107,7 @@ It’s highly recommended that you use the [Medusa UI package](https://docs.medu
|
||||
For example, you can rewrite the above component to the following:
|
||||
|
||||
```tsx title="src/admin/widgets/product-widget.tsx"
|
||||
import type { WidgetConfig } from "@medusajs/admin"
|
||||
import { defineWidgetConfig } from "@medusajs/admin-shared"
|
||||
import { Container, Heading } from "@medusajs/ui"
|
||||
|
||||
const ProductWidget = () => {
|
||||
@@ -84,72 +118,16 @@ const ProductWidget = () => {
|
||||
)
|
||||
}
|
||||
|
||||
export const config: WidgetConfig = {
|
||||
export const config: WidgetConfig = defineWidgetConfig({
|
||||
zone: "product.details.after",
|
||||
}
|
||||
})
|
||||
|
||||
export default ProductWidget
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Widget Props
|
||||
|
||||
A widget receives a `notify` prop, which is an object having the following properties:
|
||||
|
||||
- `success`: a function that shows a success toast message.
|
||||
- `error`: a function that shows an error toast message.
|
||||
- `warn`: a function that shows a warning toast message.
|
||||
- `info`: a function that shows an info toast message.
|
||||
|
||||
Each of these functions accepts two parameters: the message’s title, and the message’s content.
|
||||
|
||||
In addition, some injection zones provide additional props based on the page’s context.
|
||||
|
||||
For example, you can rewrite the above widget to the following:
|
||||
|
||||
```tsx title="src/admin/widgets/product-widget.tsx" highlights={[["14", "product.title", "Show the product's title."], ["18", "success", "Show a success toast message on the click of a button."]]}
|
||||
import type {
|
||||
WidgetConfig,
|
||||
ProductDetailsWidgetProps,
|
||||
} from "@medusajs/admin"
|
||||
import { Container, Heading, Button } from "@medusajs/ui"
|
||||
|
||||
const ProductWidget = ({
|
||||
notify,
|
||||
product,
|
||||
}: ProductDetailsWidgetProps) => {
|
||||
return (
|
||||
<Container>
|
||||
<Heading level="h2">
|
||||
Product Widget {product.title}
|
||||
</Heading>
|
||||
<Button
|
||||
onClick={() =>
|
||||
notify.success("Success!", "You clicked the button!")
|
||||
}
|
||||
className="mt-3"
|
||||
>
|
||||
Click me
|
||||
</Button>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
export const config: WidgetConfig = {
|
||||
zone: "product.details.after",
|
||||
}
|
||||
|
||||
export default ProductWidget
|
||||
```
|
||||
|
||||
Since the widget is in the product’s details page, it receives the product as a prop. The widget now shows the title of the product in the header.
|
||||
|
||||
It also shows a button that, when you click, shows a success toast message.
|
||||
|
||||
<Note title="Tip">
|
||||
|
||||
Admin Widgets support [Tailwind CSS](https://tailwindcss.com/) out of the box.
|
||||
Admin Widgets also support [Tailwind CSS](https://tailwindcss.com/) out of the box.
|
||||
|
||||
</Note>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user