Feat/SW-1519 remove deprecated hotel data from schema
* feat(SW-1519): Removed displayWebpage from hotel schema * feat(SW-1519): Removed gallery from hotel schema * feat(SW-1519): Removed conferencesAndMeetings from hotel schema * feat(SW-1519): Removed healthAndWellness from hotel schema * feat(SW-1519): Removed restaurantImages from hotel schema * feat(SW-1519): Removed restaurantsOverviewPage from hotel schema Approved-by: Fredrik Thorsson Approved-by: Matilda Landström
This commit is contained in:
@@ -73,7 +73,7 @@ export default async function HotelPage({ hotelId }: HotelPageProps) {
|
|||||||
return notFound()
|
return notFound()
|
||||||
}
|
}
|
||||||
|
|
||||||
const jsonSchema = generateHotelSchema(hotelData.hotel)
|
const jsonSchema = generateHotelSchema(hotelData)
|
||||||
const { faq, content, tabValues } = hotelPageData
|
const { faq, content, tabValues } = hotelPageData
|
||||||
const {
|
const {
|
||||||
name,
|
name,
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ export function getSubpageData(
|
|||||||
: null,
|
: null,
|
||||||
}
|
}
|
||||||
case additionalData.meetingRooms.nameInUrl:
|
case additionalData.meetingRooms.nameInUrl:
|
||||||
const meetingImage = hotel.conferencesAndMeetings?.heroImages[0]
|
const meetingImage = additionalData.conferencesAndMeetings?.heroImages[0]
|
||||||
return {
|
return {
|
||||||
elevatorPitch: hotel.hotelContent.texts.meetingDescription?.medium,
|
elevatorPitch: hotel.hotelContent.texts.meetingDescription?.medium,
|
||||||
mainBody: additionalData.meetingRooms.mainBody,
|
mainBody: additionalData.meetingRooms.mainBody,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export function getHotelPins(
|
|||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
return hotels.map(({ availability, hotel }) => {
|
return hotels.map(({ availability, hotel, additionalData }) => {
|
||||||
const productType = availability.productType
|
const productType = availability.productType
|
||||||
return {
|
return {
|
||||||
coordinates: {
|
coordinates: {
|
||||||
@@ -19,7 +19,8 @@ export function getHotelPins(
|
|||||||
name: hotel.name,
|
name: hotel.name,
|
||||||
publicPrice: productType?.public?.localPrice.pricePerNight ?? null,
|
publicPrice: productType?.public?.localPrice.pricePerNight ?? null,
|
||||||
memberPrice: productType?.member?.localPrice.pricePerNight ?? null,
|
memberPrice: productType?.member?.localPrice.pricePerNight ?? null,
|
||||||
redemptionPrice: productType?.redemption?.localPrice.pointsPerNight ?? null,
|
redemptionPrice:
|
||||||
|
productType?.redemption?.localPrice.pointsPerNight ?? null,
|
||||||
rateType:
|
rateType:
|
||||||
productType?.public?.rateType ?? productType?.member?.rateType ?? null,
|
productType?.public?.rateType ?? productType?.member?.rateType ?? null,
|
||||||
currency:
|
currency:
|
||||||
@@ -27,7 +28,10 @@ export function getHotelPins(
|
|||||||
productType?.member?.localPrice.currency ||
|
productType?.member?.localPrice.currency ||
|
||||||
currencyValue ||
|
currencyValue ||
|
||||||
"N/A",
|
"N/A",
|
||||||
images: [hotel.hotelContent.images, ...(hotel.gallery?.heroImages ?? [])],
|
images: [
|
||||||
|
hotel.hotelContent.images,
|
||||||
|
...(additionalData.gallery?.heroImages ?? []),
|
||||||
|
],
|
||||||
amenities: hotel.detailedFacilities
|
amenities: hotel.detailedFacilities
|
||||||
.map((facility) => ({
|
.map((facility) => ({
|
||||||
...facility,
|
...facility,
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ export async function MyStay({ refId }: { refId: string }) {
|
|||||||
return notFound()
|
return notFound()
|
||||||
}
|
}
|
||||||
|
|
||||||
const { booking, hotel, room } = bookingConfirmation
|
const { booking, hotel, additionalData, room } = bookingConfirmation
|
||||||
const user = await getProfileSafely()
|
const user = await getProfileSafely()
|
||||||
const bv = cookies().get("bv")?.value
|
const bv = cookies().get("bv")?.value
|
||||||
const intl = await getIntl()
|
const intl = await getIntl()
|
||||||
@@ -78,7 +78,7 @@ export async function MyStay({ refId }: { refId: string }) {
|
|||||||
<Image
|
<Image
|
||||||
className={styles.image}
|
className={styles.image}
|
||||||
src={
|
src={
|
||||||
hotel.gallery?.heroImages[0]?.imageSizes.large ??
|
additionalData.gallery?.heroImages[0]?.imageSizes.large ??
|
||||||
hotel.galleryImages[0]?.imageSizes.large ??
|
hotel.galleryImages[0]?.imageSizes.large ??
|
||||||
""
|
""
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import type {
|
|||||||
import type { CategorizedFilters } from "@/types/components/hotelReservation/selectHotel/hotelFilters"
|
import type { CategorizedFilters } from "@/types/components/hotelReservation/selectHotel/hotelFilters"
|
||||||
import { AvailabilityEnum } from "@/types/components/hotelReservation/selectHotel/selectHotel"
|
import { AvailabilityEnum } from "@/types/components/hotelReservation/selectHotel/selectHotel"
|
||||||
import type { SelectHotelSearchParams } from "@/types/components/hotelReservation/selectHotel/selectHotelSearchParams"
|
import type { SelectHotelSearchParams } from "@/types/components/hotelReservation/selectHotel/selectHotelSearchParams"
|
||||||
import type { DetailedFacility, Hotel } from "@/types/hotel"
|
import type { AdditionalData, DetailedFacility, Hotel } from "@/types/hotel"
|
||||||
import type { HotelsAvailabilityItem } from "@/types/trpc/routers/hotel/availability"
|
import type { HotelsAvailabilityItem } from "@/types/trpc/routers/hotel/availability"
|
||||||
import type {
|
import type {
|
||||||
HotelLocation,
|
HotelLocation,
|
||||||
@@ -26,6 +26,7 @@ interface AvailabilityResponse {
|
|||||||
export interface HotelResponse {
|
export interface HotelResponse {
|
||||||
availability: HotelsAvailabilityItem
|
availability: HotelsAvailabilityItem
|
||||||
hotel: Hotel
|
hotel: Hotel
|
||||||
|
additionalData: AdditionalData
|
||||||
}
|
}
|
||||||
|
|
||||||
type Result = AvailabilityResponse | null
|
type Result = AvailabilityResponse | null
|
||||||
@@ -48,6 +49,7 @@ async function enhanceHotels(hotels: HotelsAvailabilityItem[]) {
|
|||||||
return {
|
return {
|
||||||
availability,
|
availability,
|
||||||
hotel: hotelData.hotel,
|
hotel: hotelData.hotel,
|
||||||
|
additionalData: hotelData.additionalData,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
@@ -121,12 +123,12 @@ function sortAndFilterHotelsByAvailability(
|
|||||||
(hotel.productType?.public &&
|
(hotel.productType?.public &&
|
||||||
currentAddedHotel?.productType?.public &&
|
currentAddedHotel?.productType?.public &&
|
||||||
hotel.productType.public.localPrice.pricePerNight <
|
hotel.productType.public.localPrice.pricePerNight <
|
||||||
currentAddedHotel.productType.public.localPrice
|
currentAddedHotel.productType.public.localPrice
|
||||||
.pricePerNight) ||
|
.pricePerNight) ||
|
||||||
(hotel.productType?.member &&
|
(hotel.productType?.member &&
|
||||||
currentAddedHotel?.productType?.member &&
|
currentAddedHotel?.productType?.member &&
|
||||||
hotel.productType.member.localPrice.pricePerNight <
|
hotel.productType.member.localPrice.pricePerNight <
|
||||||
currentAddedHotel.productType.member.localPrice.pricePerNight)
|
currentAddedHotel.productType.member.localPrice.pricePerNight)
|
||||||
) {
|
) {
|
||||||
availableHotels.set(hotel.hotelId, hotel)
|
availableHotels.set(hotel.hotelId, hotel)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import { hotelreservation } from "@/constants/routes/hotelReservation"
|
|||||||
import { env } from "@/env/server"
|
import { env } from "@/env/server"
|
||||||
|
|
||||||
import { attributesSchema as hotelAttributesSchema } from "../../hotels/schemas/hotel"
|
import { attributesSchema as hotelAttributesSchema } from "../../hotels/schemas/hotel"
|
||||||
|
import { additionalDataAttributesSchema } from "../../hotels/schemas/hotel/include/additionalData"
|
||||||
import { tempImageVaultAssetSchema } from "../schemas/imageVault"
|
import { tempImageVaultAssetSchema } from "../schemas/imageVault"
|
||||||
import { systemSchema } from "../schemas/system"
|
import { systemSchema } from "../schemas/system"
|
||||||
import { getDescription, getImage, getTitle } from "./utils"
|
import { getDescription, getImage, getTitle } from "./utils"
|
||||||
@@ -96,7 +97,11 @@ export const rawMetadataSchema = z.object({
|
|||||||
blocks: metaDataBlocksSchema,
|
blocks: metaDataBlocksSchema,
|
||||||
hotel_page_id: z.string().optional().nullable(),
|
hotel_page_id: z.string().optional().nullable(),
|
||||||
hotelData: hotelAttributesSchema
|
hotelData: hotelAttributesSchema
|
||||||
.pick({ name: true, address: true, hotelContent: true, gallery: true })
|
.pick({ name: true, address: true, hotelContent: true })
|
||||||
|
.optional()
|
||||||
|
.nullable(),
|
||||||
|
additionalHotelData: additionalDataAttributesSchema
|
||||||
|
.pick({ gallery: true })
|
||||||
.optional()
|
.optional()
|
||||||
.nullable(),
|
.nullable(),
|
||||||
location: z.string().optional().nullable(),
|
location: z.string().optional().nullable(),
|
||||||
|
|||||||
@@ -248,7 +248,12 @@ export const metadataQueryRouter = router({
|
|||||||
|
|
||||||
metadata = await getTransformedMetadata({
|
metadata = await getTransformedMetadata({
|
||||||
...hotelPageData,
|
...hotelPageData,
|
||||||
hotelData: hotelData?.hotel,
|
...(hotelData
|
||||||
|
? {
|
||||||
|
hotelData: hotelData?.hotel,
|
||||||
|
additionalHotelData: hotelData?.additionalData,
|
||||||
|
}
|
||||||
|
: {}),
|
||||||
})
|
})
|
||||||
break
|
break
|
||||||
case PageContentTypeEnum.startPage:
|
case PageContentTypeEnum.startPage:
|
||||||
|
|||||||
@@ -157,8 +157,8 @@ export function getImage(data: RawMetadataSchema) {
|
|||||||
const metadataImage = data.web?.seo_metadata?.seo_image
|
const metadataImage = data.web?.seo_metadata?.seo_image
|
||||||
const heroImage = data.hero_image || data.header?.hero_image
|
const heroImage = data.hero_image || data.header?.hero_image
|
||||||
const hotelImage =
|
const hotelImage =
|
||||||
data.hotelData?.gallery?.heroImages?.[0] ||
|
data.additionalHotelData?.gallery?.heroImages?.[0] ||
|
||||||
data.hotelData?.gallery?.smallerImages?.[0]
|
data.additionalHotelData?.gallery?.smallerImages?.[0]
|
||||||
|
|
||||||
// Currently we don't have the possibility to get smaller images from ImageVault (2024-11-15)
|
// Currently we don't have the possibility to get smaller images from ImageVault (2024-11-15)
|
||||||
if (metadataImage) {
|
if (metadataImage) {
|
||||||
|
|||||||
@@ -13,9 +13,6 @@ import { hotelContentSchema } from "./hotel/content"
|
|||||||
import { detailedFacilitiesSchema } from "./hotel/detailedFacility"
|
import { detailedFacilitiesSchema } from "./hotel/detailedFacility"
|
||||||
import { hotelFactsSchema } from "./hotel/facts"
|
import { hotelFactsSchema } from "./hotel/facts"
|
||||||
import { healthFacilitiesSchema } from "./hotel/healthFacilities"
|
import { healthFacilitiesSchema } from "./hotel/healthFacilities"
|
||||||
import { displayWebPageSchema } from "./hotel/include/additionalData/displayWebPage"
|
|
||||||
import { facilitySchema } from "./hotel/include/additionalData/facility"
|
|
||||||
import { gallerySchema } from "./hotel/include/additionalData/gallery"
|
|
||||||
import { includeSchema } from "./hotel/include/include"
|
import { includeSchema } from "./hotel/include/include"
|
||||||
import { locationSchema } from "./hotel/location"
|
import { locationSchema } from "./hotel/location"
|
||||||
import { merchantInformationSchema } from "./hotel/merchantInformation"
|
import { merchantInformationSchema } from "./hotel/merchantInformation"
|
||||||
@@ -31,17 +28,13 @@ export const attributesSchema = z.object({
|
|||||||
address: addressSchema,
|
address: addressSchema,
|
||||||
cityId: nullableStringValidator,
|
cityId: nullableStringValidator,
|
||||||
cityName: nullableStringValidator,
|
cityName: nullableStringValidator,
|
||||||
conferencesAndMeetings: facilitySchema.nullish(),
|
|
||||||
contactInformation: contactInformationSchema,
|
contactInformation: contactInformationSchema,
|
||||||
countryCode: nullableStringValidator,
|
countryCode: nullableStringValidator,
|
||||||
detailedFacilities: detailedFacilitiesSchema,
|
detailedFacilities: detailedFacilitiesSchema,
|
||||||
displayWebPage: displayWebPageSchema,
|
|
||||||
gallery: gallerySchema.nullish(),
|
|
||||||
galleryImages: z
|
galleryImages: z
|
||||||
.array(imageSchema)
|
.array(imageSchema)
|
||||||
.nullish()
|
.nullish()
|
||||||
.transform((arr) => (arr ? arr.filter(Boolean) : [])),
|
.transform((arr) => (arr ? arr.filter(Boolean) : [])),
|
||||||
healthAndWellness: facilitySchema.nullish(),
|
|
||||||
healthFacilities: healthFacilitiesSchema,
|
healthFacilities: healthFacilitiesSchema,
|
||||||
hotelContent: hotelContentSchema,
|
hotelContent: hotelContentSchema,
|
||||||
hotelFacts: hotelFactsSchema,
|
hotelFacts: hotelFactsSchema,
|
||||||
@@ -56,7 +49,6 @@ export const attributesSchema = z.object({
|
|||||||
parking: nullableArrayObjectValidator(parkingSchema),
|
parking: nullableArrayObjectValidator(parkingSchema),
|
||||||
pointsOfInterest: pointOfInterestsSchema,
|
pointsOfInterest: pointOfInterestsSchema,
|
||||||
ratings: ratingsSchema,
|
ratings: ratingsSchema,
|
||||||
restaurantImages: facilitySchema.nullish(),
|
|
||||||
rewardNight: rewardNightSchema,
|
rewardNight: rewardNightSchema,
|
||||||
socialMedia: socialMediaSchema,
|
socialMedia: socialMediaSchema,
|
||||||
specialAlerts: specialAlertsSchema,
|
specialAlerts: specialAlertsSchema,
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { z } from "zod"
|
|||||||
import { nullableStringValidator } from "@/utils/zod/stringValidator"
|
import { nullableStringValidator } from "@/utils/zod/stringValidator"
|
||||||
|
|
||||||
import { imageSchema } from "../image"
|
import { imageSchema } from "../image"
|
||||||
import { restaurantsOverviewPageSchema } from "./include/additionalData/restaurantsOverviewPage"
|
|
||||||
|
|
||||||
const descriptionSchema = z
|
const descriptionSchema = z
|
||||||
.object({
|
.object({
|
||||||
@@ -21,6 +20,5 @@ const textsSchema = z.object({
|
|||||||
|
|
||||||
export const hotelContentSchema = z.object({
|
export const hotelContentSchema = z.object({
|
||||||
images: imageSchema,
|
images: imageSchema,
|
||||||
restaurantsOverviewPage: restaurantsOverviewPageSchema,
|
|
||||||
texts: textsSchema,
|
texts: textsSchema,
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -15,29 +15,31 @@ export const extraPageSchema = z.object({
|
|||||||
nameInUrl: nullableStringValidator,
|
nameInUrl: nullableStringValidator,
|
||||||
})
|
})
|
||||||
|
|
||||||
|
export const additionalDataAttributesSchema = z.object({
|
||||||
|
accessibility: facilitySchema.nullish(),
|
||||||
|
conferencesAndMeetings: facilitySchema.nullish(),
|
||||||
|
displayWebPage: displayWebPageSchema,
|
||||||
|
gallery: gallerySchema.nullish(),
|
||||||
|
healthAndFitness: extraPageSchema,
|
||||||
|
healthAndWellness: facilitySchema.nullish(),
|
||||||
|
hotelParking: extraPageSchema,
|
||||||
|
hotelRoomElevatorPitchText: nullableStringValidator,
|
||||||
|
hotelSpecialNeeds: extraPageSchema,
|
||||||
|
id: nullableStringValidator,
|
||||||
|
meetingRooms: extraPageSchema.merge(
|
||||||
|
z.object({
|
||||||
|
meetingOnlineLink: z.string().nullish(),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
name: nullableStringValidator,
|
||||||
|
parkingImages: facilitySchema.nullish(),
|
||||||
|
restaurantImages: facilitySchema.nullish(),
|
||||||
|
restaurantsOverviewPage: restaurantsOverviewPageSchema,
|
||||||
|
specialNeedGroups: nullableArrayObjectValidator(specialNeedGroupSchema),
|
||||||
|
})
|
||||||
|
|
||||||
export const additionalDataSchema = z.object({
|
export const additionalDataSchema = z.object({
|
||||||
attributes: z.object({
|
attributes: additionalDataAttributesSchema,
|
||||||
accessibility: facilitySchema.nullish(),
|
|
||||||
conferencesAndMeetings: facilitySchema.nullish(),
|
|
||||||
displayWebPage: displayWebPageSchema,
|
|
||||||
gallery: gallerySchema.nullish(),
|
|
||||||
healthAndFitness: extraPageSchema,
|
|
||||||
healthAndWellness: facilitySchema.nullish(),
|
|
||||||
hotelParking: extraPageSchema,
|
|
||||||
hotelRoomElevatorPitchText: nullableStringValidator,
|
|
||||||
hotelSpecialNeeds: extraPageSchema,
|
|
||||||
id: nullableStringValidator,
|
|
||||||
meetingRooms: extraPageSchema.merge(
|
|
||||||
z.object({
|
|
||||||
meetingOnlineLink: z.string().nullish(),
|
|
||||||
})
|
|
||||||
),
|
|
||||||
name: nullableStringValidator,
|
|
||||||
parkingImages: facilitySchema.nullish(),
|
|
||||||
restaurantImages: facilitySchema.nullish(),
|
|
||||||
restaurantsOverviewPage: restaurantsOverviewPageSchema,
|
|
||||||
specialNeedGroups: nullableArrayObjectValidator(specialNeedGroupSchema),
|
|
||||||
}),
|
|
||||||
type: z.literal("additionalData"),
|
type: z.literal("additionalData"),
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -2,29 +2,21 @@ import { z } from "zod"
|
|||||||
|
|
||||||
import { attributesSchema } from "@/server/routers/hotels/schemas/hotel"
|
import { attributesSchema } from "@/server/routers/hotels/schemas/hotel"
|
||||||
|
|
||||||
import { displayWebPageSchema } from "./additionalData/displayWebPage"
|
|
||||||
|
|
||||||
export const nearbyHotelsSchema = z.object({
|
export const nearbyHotelsSchema = z.object({
|
||||||
attributes: z.lazy(() =>
|
attributes: z.lazy(() =>
|
||||||
z
|
attributesSchema.pick({
|
||||||
.object({
|
address: true,
|
||||||
displayWebPage: displayWebPageSchema,
|
cityId: true,
|
||||||
})
|
cityName: true,
|
||||||
.merge(
|
detailedFacilities: true,
|
||||||
attributesSchema.pick({
|
hotelContent: true,
|
||||||
address: true,
|
isActive: true,
|
||||||
cityId: true,
|
isPublished: true,
|
||||||
cityName: true,
|
location: true,
|
||||||
detailedFacilities: true,
|
name: true,
|
||||||
hotelContent: true,
|
operaId: true,
|
||||||
isActive: true,
|
ratings: true,
|
||||||
isPublished: true,
|
})
|
||||||
location: true,
|
|
||||||
name: true,
|
|
||||||
operaId: true,
|
|
||||||
ratings: true,
|
|
||||||
})
|
|
||||||
)
|
|
||||||
),
|
),
|
||||||
id: z.string(),
|
id: z.string(),
|
||||||
type: z.literal("hotels"),
|
type: z.literal("hotels"),
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,8 +1,8 @@
|
|||||||
import type { Hotel } from "@/types/hotel"
|
import type { AdditionalData, Hotel } from "@/types/hotel"
|
||||||
import type { MeetingRooms } from "../meetingRooms"
|
import type { MeetingRooms } from "../meetingRooms"
|
||||||
|
|
||||||
export type MeetingsAndConferencesSidePeekProps = {
|
export type MeetingsAndConferencesSidePeekProps = {
|
||||||
meetingFacilities: Hotel["conferencesAndMeetings"]
|
meetingFacilities: AdditionalData["conferencesAndMeetings"]
|
||||||
descriptions: Hotel["hotelContent"]["texts"]["meetingDescription"]
|
descriptions: Hotel["hotelContent"]["texts"]["meetingDescription"]
|
||||||
meetingRooms: MeetingRooms
|
meetingRooms: MeetingRooms
|
||||||
meetingPageUrl: string | undefined
|
meetingPageUrl: string | undefined
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import type {
|
|||||||
WithContext,
|
WithContext,
|
||||||
} from "schema-dts"
|
} from "schema-dts"
|
||||||
|
|
||||||
import type { Hotel } from "@/types/hotel"
|
import type { HotelData } from "@/types/hotel"
|
||||||
import type { Breadcrumbs } from "@/types/trpc/routers/contentstack/breadcrumbs"
|
import type { Breadcrumbs } from "@/types/trpc/routers/contentstack/breadcrumbs"
|
||||||
|
|
||||||
export function generateBreadcrumbsSchema(breadcrumbs: Breadcrumbs) {
|
export function generateBreadcrumbsSchema(breadcrumbs: Breadcrumbs) {
|
||||||
@@ -32,10 +32,13 @@ export function generateBreadcrumbsSchema(breadcrumbs: Breadcrumbs) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function generateHotelSchema(hotel: Hotel) {
|
export function generateHotelSchema(hotelData: HotelData) {
|
||||||
|
const { hotel, additionalData } = hotelData
|
||||||
const ratings = hotel.ratings?.tripAdvisor
|
const ratings = hotel.ratings?.tripAdvisor
|
||||||
const checkinData = hotel.hotelFacts.checkin
|
const checkinData = hotel.hotelFacts.checkin
|
||||||
const image = hotel.gallery?.heroImages[0] || hotel.gallery?.smallerImages[0]
|
const image =
|
||||||
|
additionalData.gallery?.heroImages[0] ||
|
||||||
|
additionalData.gallery?.smallerImages[0]
|
||||||
const facilities = hotel.detailedFacilities
|
const facilities = hotel.detailedFacilities
|
||||||
const jsonLd: WithContext<HotelSchema> = {
|
const jsonLd: WithContext<HotelSchema> = {
|
||||||
"@context": "https://schema.org",
|
"@context": "https://schema.org",
|
||||||
|
|||||||
Reference in New Issue
Block a user