mirror of
https://github.com/acid-info/logos-press-engine.git
synced 2025-02-23 06:38:27 +00:00
add postcard based on new design
This commit is contained in:
parent
ce1f3fed73
commit
a94e0cc2c7
@ -10,7 +10,7 @@ import { Typography } from '@acid-info/lsd-react'
|
||||
import styled from '@emotion/styled'
|
||||
import ReactPlayer from 'react-player'
|
||||
import { LPE } from '../../types/lpe.types'
|
||||
import { PostImageRatio } from '../Post/Post'
|
||||
import { PostImageRatio } from '@/components/PostCard/PostCard'
|
||||
import { ArticleImageBlockWrapper } from './Article.ImageBlockWrapper'
|
||||
|
||||
export const RenderArticleBlock = ({
|
||||
|
@ -1,7 +1,10 @@
|
||||
import styled from '@emotion/styled'
|
||||
import Image from 'next/image'
|
||||
import { LPE } from '../../types/lpe.types'
|
||||
import { PostImageRatio, PostImageRatioOptions } from '../Post/Post'
|
||||
import {
|
||||
PostImageRatio,
|
||||
PostImageRatioOptions,
|
||||
} from '@/components/PostCard/PostCard'
|
||||
import { ResponsiveImage } from '../ResponsiveImage/ResponsiveImage'
|
||||
|
||||
type Props = {
|
||||
|
@ -6,7 +6,7 @@ import { useIntersectionObserver } from '@/utils/ui.utils'
|
||||
import { Typography } from '@acid-info/lsd-react'
|
||||
import styled from '@emotion/styled'
|
||||
import { LPE } from '../../../types/lpe.types'
|
||||
import { PostImageRatio } from '../../Post/Post'
|
||||
import { PostImageRatio } from '@/components/PostCard/PostCard'
|
||||
import { ArticleImageBlockWrapper } from '../Article.ImageBlockWrapper'
|
||||
import ArticleStats from '../Article.Stats'
|
||||
import ArticleSummary from './Article.Summary'
|
||||
|
@ -1,91 +0,0 @@
|
||||
import styled from '@emotion/styled'
|
||||
import { LPE } from '../../types/lpe.types'
|
||||
import { Grid, GridItem } from '../Grid/Grid'
|
||||
import Post, { PostSize } from '../Post/Post'
|
||||
|
||||
type Props = {
|
||||
post: LPE.Article.Data
|
||||
}
|
||||
|
||||
const FeaturedPost = ({ post }: Props) => {
|
||||
return (
|
||||
<CustomGrid>
|
||||
<GridItem className="w-16">
|
||||
<PostWrapper>
|
||||
<Post
|
||||
data={{
|
||||
authors: post.authors,
|
||||
date: post.modifiedAt ? new Date(post.modifiedAt) : null,
|
||||
slug: post.slug,
|
||||
title: post.title,
|
||||
coverImage: post.coverImage,
|
||||
description: post.subtitle,
|
||||
summary: post.summary,
|
||||
tags: post.tags,
|
||||
}}
|
||||
appearance={{
|
||||
size: PostSize.LARGE,
|
||||
imagePropsArray: [
|
||||
{
|
||||
fill: true,
|
||||
height: '432px',
|
||||
className: 'desktop',
|
||||
nextImageProps: post.coverImage
|
||||
? {
|
||||
quality: 100,
|
||||
width: post.coverImage?.width * 2,
|
||||
height: post.coverImage?.height * 2,
|
||||
}
|
||||
: {},
|
||||
},
|
||||
{
|
||||
fill: false,
|
||||
className: 'mobile',
|
||||
nextImageProps: post.coverImage
|
||||
? {
|
||||
quality: 100,
|
||||
width: post.coverImage?.width * 2,
|
||||
height: post.coverImage?.height * 2,
|
||||
}
|
||||
: {},
|
||||
},
|
||||
],
|
||||
}}
|
||||
isFeatured
|
||||
/>
|
||||
</PostWrapper>
|
||||
</GridItem>
|
||||
</CustomGrid>
|
||||
)
|
||||
}
|
||||
|
||||
const CustomGrid = styled(Grid)`
|
||||
margin-bottom: 108px;
|
||||
`
|
||||
|
||||
const PostWrapper = styled.div`
|
||||
margin-top: 16px;
|
||||
padding: 16px 0;
|
||||
border-top: 1px solid rgb(var(--lsd-theme-primary));
|
||||
width: 100%;
|
||||
|
||||
.mobile {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.desktop {
|
||||
display: block;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.desktop {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mobile {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export default FeaturedPost
|
@ -1 +0,0 @@
|
||||
export { default as FeaturedPost } from './FeaturedPost'
|
12
src/components/FeaturedPost/index.tsx
Normal file
12
src/components/FeaturedPost/index.tsx
Normal file
@ -0,0 +1,12 @@
|
||||
import { FC } from 'react'
|
||||
|
||||
interface Props {
|
||||
post: any
|
||||
}
|
||||
|
||||
// TODO: this needs to be removed and replaced by Postcard component
|
||||
export const FeaturedPost: FC<Props> = ({ post }) => {
|
||||
return null
|
||||
}
|
||||
|
||||
export default FeaturedPost
|
@ -1,272 +0,0 @@
|
||||
import { Tags } from '@/components/Tags'
|
||||
import { Typography } from '@acid-info/lsd-react'
|
||||
import { CommonProps } from '@acid-info/lsd-react/dist/utils/useCommonProps'
|
||||
import styled from '@emotion/styled'
|
||||
import Link from 'next/link'
|
||||
import React, { useMemo } from 'react'
|
||||
import { LPE } from '../../types/lpe.types'
|
||||
import { Authors } from '../Authors'
|
||||
import { AuthorsDirection } from '../Authors/Authors'
|
||||
import { LogosCircleIcon } from '../Icons/LogosCircleIcon'
|
||||
import {
|
||||
ResponsiveImage,
|
||||
ResponsiveImageProps,
|
||||
} from '../ResponsiveImage/ResponsiveImage'
|
||||
|
||||
export enum PostImageRatio {
|
||||
PORTRAIT = 'portrait',
|
||||
LANDSCAPE = 'landscape',
|
||||
SQUARE = 'square',
|
||||
}
|
||||
|
||||
export enum PostClassType {
|
||||
ARTICLE = 'article',
|
||||
PODCAST = 'podcast',
|
||||
}
|
||||
|
||||
export enum PostStyleType {
|
||||
LSD = 'lsd',
|
||||
DEFAULT = 'default',
|
||||
}
|
||||
|
||||
export enum PostSize {
|
||||
SMALL = 'small',
|
||||
LARGE = 'large',
|
||||
}
|
||||
|
||||
export enum PostType {
|
||||
BODY = 'body',
|
||||
HEADER = 'header',
|
||||
}
|
||||
|
||||
export type PostAppearanceProps = {
|
||||
size?: PostSize
|
||||
classType?: PostClassType
|
||||
postType?: PostType
|
||||
styleType?: PostStyleType
|
||||
aspectRatio?: PostImageRatio
|
||||
showImage?: boolean
|
||||
imageProps?: ResponsiveImageProps
|
||||
imagePropsArray?: ResponsiveImageProps[]
|
||||
}
|
||||
|
||||
export type PostDataProps = {
|
||||
slug: string
|
||||
date: Date | null
|
||||
title: string
|
||||
description?: string
|
||||
authors: LPE.Author.Document[]
|
||||
tags?: string[]
|
||||
coverImage?: LPE.Article.Data['coverImage'] | null
|
||||
summary?: string
|
||||
}
|
||||
|
||||
export const PostImageRatioOptions = {
|
||||
[PostImageRatio.PORTRAIT]: '9 / 16',
|
||||
[PostImageRatio.LANDSCAPE]: '16 / 9',
|
||||
[PostImageRatio.SQUARE]: '1 / 1',
|
||||
}
|
||||
|
||||
export type PostProps = CommonProps &
|
||||
React.HTMLAttributes<HTMLDivElement> & {
|
||||
appearance?: PostAppearanceProps
|
||||
data: PostDataProps
|
||||
isFeatured?: boolean
|
||||
}
|
||||
|
||||
export default function Post({
|
||||
appearance: {
|
||||
size = PostSize.SMALL,
|
||||
classType = PostClassType.ARTICLE,
|
||||
postType = PostType.BODY,
|
||||
showImage = true,
|
||||
imageProps,
|
||||
imagePropsArray = [],
|
||||
} = {},
|
||||
data: {
|
||||
coverImage = null,
|
||||
date,
|
||||
title,
|
||||
description,
|
||||
authors,
|
||||
slug,
|
||||
tags = [],
|
||||
},
|
||||
isFeatured = false,
|
||||
...props
|
||||
}: PostProps) {
|
||||
const _title = useMemo(
|
||||
() => (
|
||||
<TitleLink href={`/article/${slug}`}>
|
||||
<Title
|
||||
variant={size === PostSize.SMALL ? 'h4' : 'h1'}
|
||||
genericFontFamily="serif"
|
||||
component="h2"
|
||||
>
|
||||
{title}
|
||||
</Title>
|
||||
</TitleLink>
|
||||
),
|
||||
[title, size, slug],
|
||||
)
|
||||
|
||||
const _description = useMemo(
|
||||
() =>
|
||||
classType == PostClassType.ARTICLE && (
|
||||
<Description
|
||||
variant={size === PostSize.SMALL ? 'body2' : 'h6'}
|
||||
genericFontFamily="sans-serif"
|
||||
isFeatured={isFeatured}
|
||||
>
|
||||
{description}
|
||||
</Description>
|
||||
),
|
||||
[classType, description, isFeatured, size],
|
||||
)
|
||||
|
||||
const _thumbnail = useMemo(() => {
|
||||
if (!showImage || !coverImage) return null
|
||||
let allImageProps = [
|
||||
...imagePropsArray,
|
||||
...(imageProps ? [imageProps] : []),
|
||||
]
|
||||
|
||||
if (postType === PostType.BODY) {
|
||||
return (
|
||||
<Link href={`/article/${slug}`}>
|
||||
{allImageProps.length > 0 ? (
|
||||
allImageProps.map((_imageProps, index) => (
|
||||
<ResponsiveImage key={index} {..._imageProps} data={coverImage} />
|
||||
))
|
||||
) : (
|
||||
<ResponsiveImage {...imageProps} data={coverImage} />
|
||||
)}
|
||||
</Link>
|
||||
)
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
<Link href={`/article/${slug}`}>
|
||||
{allImageProps.length > 0 ? (
|
||||
allImageProps.map((_imageProps, index) => (
|
||||
<ResponsiveImage
|
||||
key={index}
|
||||
{..._imageProps}
|
||||
data={coverImage}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<ResponsiveImage {...imageProps} data={coverImage} />
|
||||
)}
|
||||
</Link>
|
||||
{_title}
|
||||
{_description}
|
||||
</>
|
||||
)
|
||||
}
|
||||
}, [slug, showImage, coverImage, postType, imageProps, _title, _description])
|
||||
|
||||
const _header = useMemo(() => {
|
||||
if (postType === 'body')
|
||||
return (
|
||||
<>
|
||||
<HeaderContainer isFeatured={isFeatured}>
|
||||
<Row>
|
||||
<Typography variant="body3" genericFontFamily="sans-serif">
|
||||
{classType.toUpperCase()}
|
||||
</Typography>
|
||||
<Typography variant="body3">•</Typography>
|
||||
<Typography variant="body3" genericFontFamily="sans-serif">
|
||||
{date &&
|
||||
date.toLocaleString('en-GB', {
|
||||
day: 'numeric',
|
||||
month: 'long', // TODO: Should be uppercase
|
||||
year: 'numeric',
|
||||
})}
|
||||
</Typography>
|
||||
</Row>
|
||||
{_title}
|
||||
</HeaderContainer>
|
||||
</>
|
||||
)
|
||||
}, [postType, classType, isFeatured, date, _title])
|
||||
|
||||
return (
|
||||
<Container {...props}>
|
||||
{_thumbnail}
|
||||
{_header}
|
||||
{postType === 'body' && _description}
|
||||
{classType === 'article' ? (
|
||||
<Authors
|
||||
authors={authors}
|
||||
email={false}
|
||||
flexDirection={AuthorsDirection.ROW}
|
||||
gap={8}
|
||||
/>
|
||||
) : (
|
||||
<PodcastAuthor>
|
||||
<LogosCircleIcon color="primary" />
|
||||
<Typography variant="body3" genericFontFamily="sans-serif">
|
||||
Network State
|
||||
</Typography>
|
||||
</PodcastAuthor>
|
||||
)}
|
||||
{tags.length > 0 && <Tags tags={tags} />}
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: 'relative';
|
||||
gap: 16px;
|
||||
`
|
||||
|
||||
const Row = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
`
|
||||
|
||||
const CustomTypography = styled(Typography)`
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
word-break: break-word;
|
||||
`
|
||||
|
||||
const PodcastAuthor = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
`
|
||||
|
||||
const TitleLink = styled(Link)`
|
||||
text-decoration: none;
|
||||
width: fit-content;
|
||||
`
|
||||
|
||||
const HeaderContainer = styled(CustomTypography)<{ isFeatured: boolean }>`
|
||||
@media (min-width: 768px) {
|
||||
margin-right: ${({ isFeatured }) => (isFeatured ? '178px' : '0px')};
|
||||
}
|
||||
`
|
||||
|
||||
const Description = styled(CustomTypography)<{ isFeatured: boolean }>`
|
||||
@media (min-width: 768px) {
|
||||
margin-right: ${({ isFeatured }) => (isFeatured ? '178px' : '0px')};
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
`
|
||||
|
||||
const Title = styled(CustomTypography)`
|
||||
@media (max-width: 768px) {
|
||||
font-size: 28px;
|
||||
line-height: 36px;
|
||||
}
|
||||
`
|
@ -1,2 +0,0 @@
|
||||
export { default as Post } from './Post'
|
||||
export type { PostProps } from './Post'
|
26
src/components/PostCard/PostCard.Cover.tsx
Normal file
26
src/components/PostCard/PostCard.Cover.tsx
Normal file
@ -0,0 +1,26 @@
|
||||
import React, { FC } from 'react'
|
||||
import {
|
||||
ResponsiveImage,
|
||||
ResponsiveImageProps,
|
||||
} from '@/components/ResponsiveImage/ResponsiveImage'
|
||||
import { LPE } from '@/types/lpe.types'
|
||||
import Link from 'next/link'
|
||||
|
||||
interface Props {
|
||||
imageProps: ResponsiveImageProps
|
||||
imageData: LPE.Image.Document
|
||||
playIcon?: boolean
|
||||
link: string
|
||||
}
|
||||
export const PostCardCover: FC<Props> = ({
|
||||
link,
|
||||
imageProps,
|
||||
imageData,
|
||||
playIcon,
|
||||
}) => {
|
||||
return (
|
||||
<Link href={link}>
|
||||
<ResponsiveImage {...imageProps} data={imageData} />
|
||||
</Link>
|
||||
)
|
||||
}
|
39
src/components/PostCard/PostCard.Label.tsx
Normal file
39
src/components/PostCard/PostCard.Label.tsx
Normal file
@ -0,0 +1,39 @@
|
||||
import { Typography } from '@acid-info/lsd-react'
|
||||
import React, { FC } from 'react'
|
||||
import styled from '@emotion/styled'
|
||||
import { LPE } from '@/types/lpe.types'
|
||||
import PostType = LPE.PostType
|
||||
|
||||
interface Props {
|
||||
contentType: PostType
|
||||
date: Date | null
|
||||
}
|
||||
|
||||
export const PostCardLabel: FC<Props> = ({ contentType, date }) => {
|
||||
return (
|
||||
<Container>
|
||||
<Typography variant="body3" genericFontFamily="sans-serif">
|
||||
{contentType.toUpperCase()}
|
||||
</Typography>
|
||||
|
||||
<Typography variant="body3">•</Typography>
|
||||
|
||||
<Typography variant="body3" genericFontFamily="sans-serif">
|
||||
{date &&
|
||||
date.toLocaleString('en-GB', {
|
||||
day: 'numeric',
|
||||
month: 'long', // TODO: Should be uppercase
|
||||
year: 'numeric',
|
||||
})}
|
||||
</Typography>
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 8px;
|
||||
`
|
18
src/components/PostCard/PostCard.ShowDetails.tsx
Normal file
18
src/components/PostCard/PostCard.ShowDetails.tsx
Normal file
@ -0,0 +1,18 @@
|
||||
import { LPE } from '@/types/lpe.types'
|
||||
import Link from 'next/link'
|
||||
|
||||
export interface PostCardShowDetailsProps {
|
||||
title: string
|
||||
slug: string
|
||||
episodeNumber: number
|
||||
logo: LPE.Image.Document
|
||||
}
|
||||
|
||||
// TODO
|
||||
export const PostCardShowDetails = (props: PostCardShowDetailsProps) => {
|
||||
return (
|
||||
<Link href={`/podcast/${props.slug}`}>
|
||||
<span>{props.title}</span>
|
||||
</Link>
|
||||
)
|
||||
}
|
137
src/components/PostCard/PostCard.tsx
Normal file
137
src/components/PostCard/PostCard.tsx
Normal file
@ -0,0 +1,137 @@
|
||||
import { Tags } from '@/components/Tags'
|
||||
import { Typography } from '@acid-info/lsd-react'
|
||||
import { CommonProps } from '@acid-info/lsd-react/dist/utils/useCommonProps'
|
||||
import styled from '@emotion/styled'
|
||||
import Link from 'next/link'
|
||||
import React from 'react'
|
||||
import { LPE } from '../../types/lpe.types'
|
||||
import { Authors } from '../Authors'
|
||||
import { AuthorsDirection } from '../Authors/Authors'
|
||||
|
||||
import { ResponsiveImageProps } from '../ResponsiveImage/ResponsiveImage'
|
||||
|
||||
import { PostCardLabel } from './PostCard.Label'
|
||||
import { PostCardCover } from '@/components/PostCard/PostCard.Cover'
|
||||
import {
|
||||
PostCardShowDetails,
|
||||
PostCardShowDetailsProps,
|
||||
} from '@/components/PostCard/PostCard.ShowDetails'
|
||||
|
||||
export type PostAppearanceProps = {
|
||||
imageProps?: ResponsiveImageProps
|
||||
}
|
||||
|
||||
export type PostDataProps = {
|
||||
slug: string
|
||||
date: Date | null
|
||||
title: string
|
||||
subtitle?: string
|
||||
authors?: LPE.Author.Document[]
|
||||
tags?: string[]
|
||||
coverImage?: LPE.Article.Data['coverImage'] | null
|
||||
podcastShowDetails?: PostCardShowDetailsProps
|
||||
}
|
||||
|
||||
export type PostCardProps = CommonProps &
|
||||
React.HTMLAttributes<HTMLDivElement> & {
|
||||
appearance?: PostAppearanceProps
|
||||
data: PostDataProps
|
||||
isFeatured?: boolean
|
||||
contentType: LPE.PostType
|
||||
}
|
||||
|
||||
export const PostCard = (_props: PostCardProps) => {
|
||||
const {
|
||||
appearance: { imageProps = {} } = {},
|
||||
data: {
|
||||
coverImage = null,
|
||||
date,
|
||||
title,
|
||||
subtitle,
|
||||
authors,
|
||||
slug,
|
||||
tags = [],
|
||||
podcastShowDetails,
|
||||
},
|
||||
contentType,
|
||||
isFeatured = false,
|
||||
...props
|
||||
} = _props
|
||||
|
||||
return (
|
||||
<Container {...props}>
|
||||
{coverImage && (
|
||||
<PostCardCover
|
||||
imageProps={imageProps}
|
||||
imageData={coverImage}
|
||||
link={`/article/${slug}`}
|
||||
/>
|
||||
)}
|
||||
|
||||
<PostCardLabel contentType={contentType} date={date} />
|
||||
|
||||
<TitleLink href={`/article/${slug}`}>
|
||||
<Title genericFontFamily="serif" component="h3">
|
||||
{title}
|
||||
</Title>
|
||||
</TitleLink>
|
||||
|
||||
{subtitle && (
|
||||
<Subtitle variant={'body1'} genericFontFamily="sans-serif">
|
||||
{subtitle}
|
||||
</Subtitle>
|
||||
)}
|
||||
|
||||
{authors && authors.length > 0 && (
|
||||
<Authors
|
||||
authors={authors}
|
||||
email={false}
|
||||
flexDirection={AuthorsDirection.ROW}
|
||||
gap={8}
|
||||
/>
|
||||
)}
|
||||
{podcastShowDetails && <PostCardShowDetails {...podcastShowDetails} />}
|
||||
{tags.length > 0 && <Tags tags={tags} />}
|
||||
</Container>
|
||||
)
|
||||
}
|
||||
|
||||
const Container = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
position: 'relative';
|
||||
gap: 16px;
|
||||
`
|
||||
|
||||
const CustomTypography = styled(Typography)`
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
word-break: break-word;
|
||||
`
|
||||
|
||||
const PodcastAuthor = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
`
|
||||
|
||||
const TitleLink = styled(Link)`
|
||||
text-decoration: none;
|
||||
width: fit-content;
|
||||
`
|
||||
|
||||
const Subtitle = styled(CustomTypography)`
|
||||
@media (min-width: 768px) {
|
||||
}
|
||||
@media (max-width: 768px) {
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
}
|
||||
`
|
||||
|
||||
const Title = styled(CustomTypography)`
|
||||
@media (max-width: 768px) {
|
||||
font-size: 28px;
|
||||
line-height: 36px;
|
||||
}
|
||||
`
|
2
src/components/PostCard/index.ts
Normal file
2
src/components/PostCard/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export type { PostCardProps } from './PostCard'
|
||||
export { PostCard } from './PostCard'
|
@ -4,7 +4,8 @@ import styled from '@emotion/styled'
|
||||
import { useEffect, useState } from 'react'
|
||||
import { LPE } from '../../types/lpe.types'
|
||||
import { Grid, GridItem } from '../Grid/Grid'
|
||||
import Post from '../Post/Post'
|
||||
import { PostCard } from '@/components/PostCard'
|
||||
import PostTypes = LPE.PostTypes
|
||||
|
||||
type Props = {
|
||||
posts: LPE.Article.Data[]
|
||||
@ -57,17 +58,17 @@ export const PostsList = (props: Props) => {
|
||||
key={index}
|
||||
>
|
||||
<PostWrapper className={props.loading ? 'loading' : ''}>
|
||||
<Post
|
||||
<PostCard
|
||||
data={{
|
||||
authors: post.authors,
|
||||
date: post.modifiedAt ? new Date(post.modifiedAt) : null,
|
||||
slug: post.slug,
|
||||
title: post.title,
|
||||
description: post.subtitle,
|
||||
subtitle: post.subtitle,
|
||||
coverImage: post.coverImage,
|
||||
summary: post.summary,
|
||||
tags: post.tags,
|
||||
}}
|
||||
contentType={PostTypes.Article}
|
||||
/>
|
||||
</PostWrapper>
|
||||
</GridItem>
|
||||
|
Loading…
x
Reference in New Issue
Block a user