feat(dashboard): update create fulfillment UI part 1 (#8972)
**What** - update Create fulfillment modal according to the design **Note** - in a followup I will add support for inventory kits **Question** - should we support overriding shipping method as per design? --- Before:  After: 
This commit is contained in:
@@ -1076,6 +1076,7 @@
|
|||||||
"itemsToFulfillDesc": "Choose items and quantities to fulfill",
|
"itemsToFulfillDesc": "Choose items and quantities to fulfill",
|
||||||
"locationDescription": "Choose which location you want to fulfill items from.",
|
"locationDescription": "Choose which location you want to fulfill items from.",
|
||||||
"sendNotificationHint": "Notify customers about the created fulfillment.",
|
"sendNotificationHint": "Notify customers about the created fulfillment.",
|
||||||
|
"methodDescription": "Choose a different shipping method from the one customer selected",
|
||||||
"error": {
|
"error": {
|
||||||
"wrongQuantity": "Only one item is available for fulfillment",
|
"wrongQuantity": "Only one item is available for fulfillment",
|
||||||
"wrongQuantity_other": "Quantity should be a number between 1 and {{number}}",
|
"wrongQuantity_other": "Quantity should be a number between 1 and {{number}}",
|
||||||
@@ -2464,6 +2465,7 @@
|
|||||||
"newPassword": "New Password",
|
"newPassword": "New Password",
|
||||||
"repeatNewPassword": "Repeat New Password",
|
"repeatNewPassword": "Repeat New Password",
|
||||||
"categories": "Categories",
|
"categories": "Categories",
|
||||||
|
"shippingMethod": "Shipping method",
|
||||||
"configurations": "Configurations",
|
"configurations": "Configurations",
|
||||||
"conditions": "Conditions",
|
"conditions": "Conditions",
|
||||||
"category": "Category",
|
"category": "Category",
|
||||||
|
|||||||
+1
@@ -4,5 +4,6 @@ export const CreateFulfillmentSchema = z.object({
|
|||||||
quantity: z.record(z.string(), z.number()),
|
quantity: z.record(z.string(), z.number()),
|
||||||
|
|
||||||
location_id: z.string(),
|
location_id: z.string(),
|
||||||
|
shipping_option_id: z.string().optional(),
|
||||||
send_notification: z.boolean().optional(),
|
send_notification: z.boolean().optional(),
|
||||||
})
|
})
|
||||||
|
|||||||
+85
-65
@@ -16,8 +16,9 @@ import {
|
|||||||
import { useCreateOrderFulfillment } from "../../../../../hooks/api/orders"
|
import { useCreateOrderFulfillment } from "../../../../../hooks/api/orders"
|
||||||
import { useStockLocations } from "../../../../../hooks/api/stock-locations"
|
import { useStockLocations } from "../../../../../hooks/api/stock-locations"
|
||||||
import { getFulfillableQuantity } from "../../../../../lib/order-item"
|
import { getFulfillableQuantity } from "../../../../../lib/order-item"
|
||||||
import { CreateFulfillmentSchema } from "./constants"
|
|
||||||
import { OrderCreateFulfillmentItem } from "./order-create-fulfillment-item"
|
import { OrderCreateFulfillmentItem } from "./order-create-fulfillment-item"
|
||||||
|
import { CreateFulfillmentSchema } from "./constants"
|
||||||
|
import { useShippingOptions } from "../../../../../hooks/api"
|
||||||
|
|
||||||
type OrderCreateFulfillmentFormProps = {
|
type OrderCreateFulfillmentFormProps = {
|
||||||
order: AdminOrder
|
order: AdminOrder
|
||||||
@@ -38,24 +39,23 @@ export function OrderCreateFulfillmentForm({
|
|||||||
|
|
||||||
const form = useForm<zod.infer<typeof CreateFulfillmentSchema>>({
|
const form = useForm<zod.infer<typeof CreateFulfillmentSchema>>({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
quantity: fulfillableItems.reduce(
|
quantity: fulfillableItems.reduce((acc, item) => {
|
||||||
(acc, item) => {
|
acc[item.id] = getFulfillableQuantity(item)
|
||||||
acc[item.id] = getFulfillableQuantity(item)
|
return acc
|
||||||
return acc
|
}, {} as Record<string, number>),
|
||||||
},
|
|
||||||
{} as Record<string, number>
|
|
||||||
),
|
|
||||||
send_notification: !order.no_notification,
|
send_notification: !order.no_notification,
|
||||||
},
|
},
|
||||||
resolver: zodResolver(CreateFulfillmentSchema),
|
resolver: zodResolver(CreateFulfillmentSchema),
|
||||||
})
|
})
|
||||||
|
|
||||||
const { stock_locations = [] } = useStockLocations()
|
const { stock_locations = [] } = useStockLocations()
|
||||||
|
const { shipping_options = [] } = useShippingOptions()
|
||||||
|
|
||||||
const handleSubmit = form.handleSubmit(async (data) => {
|
const handleSubmit = form.handleSubmit(async (data) => {
|
||||||
try {
|
try {
|
||||||
await createOrderFulfillment({
|
await createOrderFulfillment({
|
||||||
location_id: data.location_id,
|
location_id: data.location_id,
|
||||||
|
// shipping_option_id: data.shipping_option_id,
|
||||||
no_notification: !data.send_notification,
|
no_notification: !data.send_notification,
|
||||||
items: Object.entries(data.quantity)
|
items: Object.entries(data.quantity)
|
||||||
.filter(([, value]) => !!value)
|
.filter(([, value]) => !!value)
|
||||||
@@ -78,23 +78,6 @@ export function OrderCreateFulfillmentForm({
|
|||||||
}
|
}
|
||||||
}, [stock_locations?.length])
|
}, [stock_locations?.length])
|
||||||
|
|
||||||
const onItemRemove = (itemId: string) => {
|
|
||||||
setFulfillableItems((state) => state.filter((i) => i.id !== itemId))
|
|
||||||
form.unregister(`quantity.${itemId}`)
|
|
||||||
}
|
|
||||||
|
|
||||||
const resetItems = () => {
|
|
||||||
const items = (order.items || []).filter(
|
|
||||||
(item) => getFulfillableQuantity(item) > 0
|
|
||||||
)
|
|
||||||
setFulfillableItems(items)
|
|
||||||
|
|
||||||
items.forEach((i) =>
|
|
||||||
form.register(`quantity.${i.id}`, { value: getFulfillableQuantity(i) })
|
|
||||||
)
|
|
||||||
form.clearErrors("root")
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectedLocationId = useWatch({
|
const selectedLocationId = useWatch({
|
||||||
name: "location_id",
|
name: "location_id",
|
||||||
control: form.control,
|
control: form.control,
|
||||||
@@ -119,13 +102,10 @@ export function OrderCreateFulfillmentForm({
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const quantityMap = itemsToFulfill.reduce(
|
const quantityMap = itemsToFulfill.reduce((acc, item) => {
|
||||||
(acc, item) => {
|
acc[item.id] = getFulfillableQuantity(item as OrderLineItemDTO)
|
||||||
acc[item.id] = getFulfillableQuantity(item as OrderLineItemDTO)
|
return acc
|
||||||
return acc
|
}, {} as Record<string, number>)
|
||||||
},
|
|
||||||
{} as Record<string, number>
|
|
||||||
)
|
|
||||||
|
|
||||||
form.setValue("quantity", quantityMap)
|
form.setValue("quantity", quantityMap)
|
||||||
}, [...fulfilledQuantityArray])
|
}, [...fulfilledQuantityArray])
|
||||||
@@ -152,41 +132,91 @@ export function OrderCreateFulfillmentForm({
|
|||||||
<RouteFocusModal.Body className="flex h-full w-full flex-col items-center divide-y overflow-y-auto">
|
<RouteFocusModal.Body className="flex h-full w-full flex-col items-center divide-y overflow-y-auto">
|
||||||
<div className="flex size-full flex-col items-center overflow-auto p-16">
|
<div className="flex size-full flex-col items-center overflow-auto p-16">
|
||||||
<div className="flex w-full max-w-[736px] flex-col justify-center px-2 pb-2">
|
<div className="flex w-full max-w-[736px] flex-col justify-center px-2 pb-2">
|
||||||
<div className="flex flex-col divide-y">
|
<div className="flex flex-col divide-y divide-dashed">
|
||||||
<div className="flex-1">
|
<div className="pb-8">
|
||||||
<Form.Field
|
<Form.Field
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="location_id"
|
name="location_id"
|
||||||
render={({ field: { onChange, ref, ...field } }) => {
|
render={({ field: { onChange, ref, ...field } }) => {
|
||||||
return (
|
return (
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<Form.Label>{t("fields.location")}</Form.Label>
|
<div className="flex flex-col gap-2 xl:flex-row xl:items-center">
|
||||||
<Form.Hint>
|
<div className="flex-1">
|
||||||
{t("orders.fulfillment.locationDescription")}
|
<Form.Label>{t("fields.location")}</Form.Label>
|
||||||
</Form.Hint>
|
<Form.Hint>
|
||||||
<Form.Control>
|
{t("orders.fulfillment.locationDescription")}
|
||||||
<Select onValueChange={onChange} {...field}>
|
</Form.Hint>
|
||||||
<Select.Trigger
|
</div>
|
||||||
className="bg-ui-bg-base"
|
<div className="flex-1">
|
||||||
ref={ref}
|
<Form.Control>
|
||||||
>
|
<Select onValueChange={onChange} {...field}>
|
||||||
<Select.Value />
|
<Select.Trigger
|
||||||
</Select.Trigger>
|
className="bg-ui-bg-base"
|
||||||
<Select.Content>
|
ref={ref}
|
||||||
{stock_locations.map((l) => (
|
>
|
||||||
<Select.Item key={l.id} value={l.id}>
|
<Select.Value />
|
||||||
{l.name}
|
</Select.Trigger>
|
||||||
</Select.Item>
|
<Select.Content>
|
||||||
))}
|
{stock_locations.map((l) => (
|
||||||
</Select.Content>
|
<Select.Item key={l.id} value={l.id}>
|
||||||
</Select>
|
{l.name}
|
||||||
</Form.Control>
|
</Select.Item>
|
||||||
|
))}
|
||||||
|
</Select.Content>
|
||||||
|
</Select>
|
||||||
|
</Form.Control>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<Form.ErrorMessage />
|
<Form.ErrorMessage />
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/*<div className="py-8">*/}
|
||||||
|
{/* <Form.Field*/}
|
||||||
|
{/* control={form.control}*/}
|
||||||
|
{/* name="shipping_option_id"*/}
|
||||||
|
{/* render={({ field: { onChange, ref, ...field } }) => {*/}
|
||||||
|
{/* return (*/}
|
||||||
|
{/* <Form.Item>*/}
|
||||||
|
{/* <div className="flex flex-col gap-2 xl:flex-row xl:items-center">*/}
|
||||||
|
{/* <div className="flex-1">*/}
|
||||||
|
{/* <Form.Label>*/}
|
||||||
|
{/* {t("fields.shippingMethod")}*/}
|
||||||
|
{/* </Form.Label>*/}
|
||||||
|
{/* <Form.Hint>*/}
|
||||||
|
{/* {t("orders.fulfillment.methodDescription")}*/}
|
||||||
|
{/* </Form.Hint>*/}
|
||||||
|
{/* </div>*/}
|
||||||
|
{/* <div className="flex-1">*/}
|
||||||
|
{/* <Form.Control>*/}
|
||||||
|
{/* <Select onValueChange={onChange} {...field}>*/}
|
||||||
|
{/* <Select.Trigger*/}
|
||||||
|
{/* className="bg-ui-bg-base"*/}
|
||||||
|
{/* ref={ref}*/}
|
||||||
|
{/* >*/}
|
||||||
|
{/* <Select.Value />*/}
|
||||||
|
{/* </Select.Trigger>*/}
|
||||||
|
{/* <Select.Content>*/}
|
||||||
|
{/* {shipping_options.map((o) => (*/}
|
||||||
|
{/* <Select.Item key={o.id} value={o.id}>*/}
|
||||||
|
{/* {o.name}*/}
|
||||||
|
{/* </Select.Item>*/}
|
||||||
|
{/* ))}*/}
|
||||||
|
{/* </Select.Content>*/}
|
||||||
|
{/* </Select>*/}
|
||||||
|
{/* </Form.Control>*/}
|
||||||
|
{/* </div>*/}
|
||||||
|
{/* </div>*/}
|
||||||
|
{/* <Form.ErrorMessage />*/}
|
||||||
|
{/* </Form.Item>*/}
|
||||||
|
{/* )*/}
|
||||||
|
{/* }}*/}
|
||||||
|
{/* />*/}
|
||||||
|
{/*</div>*/}
|
||||||
|
<div>
|
||||||
<Form.Item className="mt-8">
|
<Form.Item className="mt-8">
|
||||||
<Form.Label>
|
<Form.Label>
|
||||||
{t("orders.fulfillment.itemsToFulfill")}
|
{t("orders.fulfillment.itemsToFulfill")}
|
||||||
@@ -202,9 +232,7 @@ export function OrderCreateFulfillmentForm({
|
|||||||
key={item.id}
|
key={item.id}
|
||||||
form={form}
|
form={form}
|
||||||
item={item}
|
item={item}
|
||||||
onItemRemove={onItemRemove}
|
|
||||||
locationId={selectedLocationId}
|
locationId={selectedLocationId}
|
||||||
currencyCode={order.currency_code}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
@@ -218,14 +246,6 @@ export function OrderCreateFulfillmentForm({
|
|||||||
classNameInner="flex justify-between flex-1 items-center"
|
classNameInner="flex justify-between flex-1 items-center"
|
||||||
>
|
>
|
||||||
{form.formState.errors.root.message}
|
{form.formState.errors.root.message}
|
||||||
<Button
|
|
||||||
variant="transparent"
|
|
||||||
size="small"
|
|
||||||
type="button"
|
|
||||||
onClick={resetItems}
|
|
||||||
>
|
|
||||||
{t("actions.reset")}
|
|
||||||
</Button>
|
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+82
-87
@@ -1,19 +1,16 @@
|
|||||||
import { Trash } from "@medusajs/icons"
|
|
||||||
import { useMemo } from "react"
|
import { useMemo } from "react"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import * as zod from "zod"
|
import * as zod from "zod"
|
||||||
import { Input, Text } from "@medusajs/ui"
|
import { Input, Text } from "@medusajs/ui"
|
||||||
import { UseFormReturn } from "react-hook-form"
|
import { UseFormReturn } from "react-hook-form"
|
||||||
|
|
||||||
import { ActionMenu } from "../../../../../components/common/action-menu/index.ts"
|
|
||||||
import { Form } from "../../../../../components/common/form/index.ts"
|
|
||||||
import { Thumbnail } from "../../../../../components/common/thumbnail/index.ts"
|
|
||||||
import { MoneyAmountCell } from "../../../../../components/table/table-cells/common/money-amount-cell/index.ts"
|
|
||||||
import { useProductVariant } from "../../../../../hooks/api/products.tsx"
|
|
||||||
import { getFulfillableQuantity } from "../../../../../lib/order-item.ts"
|
|
||||||
import { CreateFulfillmentSchema } from "./constants.ts"
|
|
||||||
import { HttpTypes } from "@medusajs/types"
|
import { HttpTypes } from "@medusajs/types"
|
||||||
|
|
||||||
|
import { Form } from "../../../../../components/common/form/index"
|
||||||
|
import { Thumbnail } from "../../../../../components/common/thumbnail/index"
|
||||||
|
import { useProductVariant } from "../../../../../hooks/api/products"
|
||||||
|
import { getFulfillableQuantity } from "../../../../../lib/order-item"
|
||||||
|
import { CreateFulfillmentSchema } from "./constants"
|
||||||
|
|
||||||
type OrderEditItemProps = {
|
type OrderEditItemProps = {
|
||||||
item: HttpTypes.AdminOrderLineItem
|
item: HttpTypes.AdminOrderLineItem
|
||||||
currencyCode: string
|
currencyCode: string
|
||||||
@@ -24,10 +21,8 @@ type OrderEditItemProps = {
|
|||||||
|
|
||||||
export function OrderCreateFulfillmentItem({
|
export function OrderCreateFulfillmentItem({
|
||||||
item,
|
item,
|
||||||
currencyCode,
|
|
||||||
form,
|
form,
|
||||||
locationId,
|
locationId,
|
||||||
onItemRemove,
|
|
||||||
}: OrderEditItemProps) {
|
}: OrderEditItemProps) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
@@ -39,8 +34,6 @@ export function OrderCreateFulfillmentItem({
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
const hasInventoryItem = !!variant?.inventory.length
|
|
||||||
|
|
||||||
const { availableQuantity, inStockQuantity } = useMemo(() => {
|
const { availableQuantity, inStockQuantity } = useMemo(() => {
|
||||||
if (!variant || !locationId) {
|
if (!variant || !locationId) {
|
||||||
return {}
|
return {}
|
||||||
@@ -70,8 +63,8 @@ export function OrderCreateFulfillmentItem({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-ui-bg-subtle shadow-elevation-card-rest my-2 rounded-xl ">
|
<div className="bg-ui-bg-subtle shadow-elevation-card-rest my-2 rounded-xl ">
|
||||||
<div className="flex gap-x-2 border-b p-3 text-sm">
|
<div className="flex flex-col gap-x-2 gap-y-2 border-b p-3 text-sm sm:flex-row">
|
||||||
<div className="flex flex-grow items-center gap-x-3">
|
<div className="flex flex-1 items-center gap-x-3">
|
||||||
<Thumbnail src={item.thumbnail} />
|
<Thumbnail src={item.thumbnail} />
|
||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div>
|
<div>
|
||||||
@@ -86,84 +79,86 @@ export function OrderCreateFulfillmentItem({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="text-ui-fg-subtle txt-small mr-2 flex flex-shrink-0 flex-col items-center">
|
<div className="flex flex-1 items-center gap-x-1">
|
||||||
<MoneyAmountCell
|
<div className="mr-2 block h-[16px] w-[2px] bg-gray-200" />
|
||||||
className="justify-end"
|
|
||||||
currencyCode={currencyCode}
|
<div className="text-small flex flex-1 flex-col">
|
||||||
amount={item.total}
|
<span className="text-ui-fg-subtle font-medium">
|
||||||
/>
|
{t("orders.fulfillment.available")}
|
||||||
{hasInventoryItem && (
|
|
||||||
<span>
|
|
||||||
{t("orders.fulfillment.available")}: {availableQuantity || "N/A"}{" "}
|
|
||||||
· {t("orders.fulfillment.inStock")}: {inStockQuantity || "N/A"}
|
|
||||||
</span>
|
</span>
|
||||||
)}
|
<span className="text-ui-fg-subtle">
|
||||||
</div>
|
{availableQuantity || "N/A"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center">
|
<div className="flex flex-1 items-center gap-x-1">
|
||||||
<ActionMenu
|
<div className="mr-2 block h-[16px] w-[2px] bg-gray-200" />
|
||||||
groups={[
|
|
||||||
{
|
|
||||||
actions: [
|
|
||||||
{
|
|
||||||
label: t("actions.remove"),
|
|
||||||
icon: <Trash />,
|
|
||||||
onClick: () => onItemRemove(item.id),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="block p-3 text-sm">
|
<div className="flex flex-col">
|
||||||
<div className="flex-1">
|
<span className="text-ui-fg-subtle font-medium">
|
||||||
<Text weight="plus" className="txt-small mb-2">
|
{t("orders.fulfillment.inStock")}
|
||||||
{t("fields.quantity")}
|
</span>
|
||||||
</Text>
|
<span className="text-ui-fg-subtle">
|
||||||
<Form.Field
|
{inStockQuantity || "N/A"}{" "}
|
||||||
control={form.control}
|
{inStockQuantity && (
|
||||||
name={`quantity.${item.id}`}
|
<span className="font-medium text-red-500">
|
||||||
rules={{ required: true, min: minValue, max: maxValue }}
|
-{form.getValues(`quantity.${item.id}`)}
|
||||||
render={({ field }) => {
|
</span>
|
||||||
return (
|
)}
|
||||||
<Form.Item>
|
</span>
|
||||||
<Form.Control>
|
</div>
|
||||||
<Input
|
</div>
|
||||||
className="bg-ui-bg-base txt-small w-full rounded-lg"
|
|
||||||
type="number"
|
|
||||||
{...field}
|
|
||||||
onChange={(e) => {
|
|
||||||
const val =
|
|
||||||
e.target.value === "" ? null : Number(e.target.value)
|
|
||||||
|
|
||||||
field.onChange(val)
|
<div className="flex flex-1 items-center gap-1">
|
||||||
|
<Form.Field
|
||||||
|
control={form.control}
|
||||||
|
name={`quantity.${item.id}`}
|
||||||
|
rules={{ required: true, min: minValue, max: maxValue }}
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<Form.Item>
|
||||||
|
<Form.Control>
|
||||||
|
<Input
|
||||||
|
className="bg-ui-bg-base txt-small w-[50px] rounded-lg text-right [appearance:textfield] [&::-webkit-inner-spin-button]:appearance-none [&::-webkit-outer-spin-button]:appearance-none"
|
||||||
|
type="number"
|
||||||
|
{...field}
|
||||||
|
onChange={(e) => {
|
||||||
|
const val =
|
||||||
|
e.target.value === ""
|
||||||
|
? null
|
||||||
|
: Number(e.target.value)
|
||||||
|
|
||||||
if (!isNaN(val)) {
|
field.onChange(val)
|
||||||
if (val < minValue || val > maxValue) {
|
|
||||||
form.setError(`quantity.${item.id}`, {
|
if (!isNaN(val)) {
|
||||||
type: "manual",
|
if (val < minValue || val > maxValue) {
|
||||||
message: t(
|
form.setError(`quantity.${item.id}`, {
|
||||||
"orders.fulfillment.error.wrongQuantity",
|
type: "manual",
|
||||||
{
|
message: t(
|
||||||
count: maxValue,
|
"orders.fulfillment.error.wrongQuantity",
|
||||||
number: maxValue,
|
{
|
||||||
}
|
count: maxValue,
|
||||||
),
|
number: maxValue,
|
||||||
})
|
}
|
||||||
} else {
|
),
|
||||||
form.clearErrors(`quantity.${item.id}`)
|
})
|
||||||
|
} else {
|
||||||
|
form.clearErrors(`quantity.${item.id}`)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}}
|
||||||
}}
|
/>
|
||||||
/>
|
</Form.Control>
|
||||||
</Form.Control>
|
<Form.ErrorMessage />
|
||||||
<Form.ErrorMessage />
|
</Form.Item>
|
||||||
</Form.Item>
|
)
|
||||||
)
|
}}
|
||||||
}}
|
/>
|
||||||
/>
|
|
||||||
|
<span className="text-ui-fg-subtle">
|
||||||
|
/ {item.quantity} {t("fields.qty")}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user