QCast 로그인 후 메인화면 작업중 ( pwdInitYn : 'Y') 로 비밀번호 변경팝업은 임시 패스
This commit is contained in:
parent
b15b263c75
commit
1c5747fddd
@ -1,9 +1,303 @@
|
||||
'use client'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { useRecoilState, useRecoilValue, useSetRecoilState } from 'recoil'
|
||||
import { modalContent, modalState, modalProps } from '@/store/modalAtom'
|
||||
import { sessionStore } from '@/store/commonAtom'
|
||||
import { useAxios } from '@/hooks/useAxios'
|
||||
import { globalLocaleStore } from '@/store/localeAtom'
|
||||
import MainContents from './main/MainContents'
|
||||
import { useMessage } from '@/hooks/useMessage'
|
||||
import { isObjectNotEmpty, queryStringFormatter } from '@/util/common-utils'
|
||||
import { stuffSearchState } from '@/store/stuffAtom'
|
||||
|
||||
export default function MainPage(props) {
|
||||
const [sessionState, setSessionState] = useRecoilState(sessionStore)
|
||||
const [open, setOpen] = useRecoilState(modalState)
|
||||
const setContent = useSetRecoilState(modalContent)
|
||||
const setModalProps = useSetRecoilState(modalProps)
|
||||
|
||||
const globalLocaleState = useRecoilValue(globalLocaleStore)
|
||||
|
||||
const { get, patch } = useAxios(globalLocaleState)
|
||||
const router = useRouter()
|
||||
const { getMessage } = useMessage()
|
||||
|
||||
const [searchTxt, setSearchTxt] = useState('')
|
||||
|
||||
const [searchRadioType, setSearchRadioType] = useState('object')
|
||||
//컨텐츠 관련
|
||||
const [saleStoreId, setSaleStoreId] = useState('')
|
||||
const [saleStoreName, setSaleStoreName] = useState('')
|
||||
|
||||
const [objectList, setObjectList] = useState([])
|
||||
const [businessCharger, setBusinessCharger] = useState('')
|
||||
const [businessChargerMail, setBusinessChargerMail] = useState('')
|
||||
const [businessChargerTel, setBusinessChargerTel] = useState('')
|
||||
|
||||
const [stuffSearch, setStuffSearch] = useRecoilState(stuffSearchState)
|
||||
|
||||
//비밀번호 변경
|
||||
const updateProcess = async (formData) => {
|
||||
const pwd1 = formData.get('password1')
|
||||
const pwd2 = formData.get('password2')
|
||||
|
||||
if (pwd1 !== pwd2) {
|
||||
alert('입력한 패스워드가 다릅니다')
|
||||
return false
|
||||
}
|
||||
|
||||
//패스워드 길이수 체크
|
||||
if (checkLength(pwd1) > 10) {
|
||||
alert('반각10자이내로 입력')
|
||||
}
|
||||
|
||||
const param = {
|
||||
loginId: sessionState.userId,
|
||||
chgType: 'I',
|
||||
chgPwd: formData.get('password1'),
|
||||
}
|
||||
|
||||
await patch({ url: '/api/login/v1.0/user/change-password', data: param }).then((res) => {
|
||||
if (res) {
|
||||
if (res.result.resultCode === 'S') {
|
||||
alert('비밀번호가 변경되었습니다.')
|
||||
setSessionState({ ...sessionState, pwdInitYn: 'Y' })
|
||||
setOpen(false)
|
||||
} else {
|
||||
alert(res.result.resultMsg)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
//자리수체크
|
||||
const checkLength = (pwd1) => {
|
||||
let str = new String(pwd1)
|
||||
let _byte = 0
|
||||
if (str.length !== 0) {
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
let str2 = str.charAt(i)
|
||||
if (encodeURIComponent(str2).length > 4) {
|
||||
_byte += 2
|
||||
} else {
|
||||
_byte++
|
||||
}
|
||||
}
|
||||
}
|
||||
return _byte
|
||||
}
|
||||
|
||||
//공백제거
|
||||
const checkValue = (e) => {
|
||||
let spaceChk = /\s/
|
||||
if (spaceChk.exec(e.target.value)) {
|
||||
e.target.value = e.target.value.replace(/\s|/gi, '')
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
if (sessionState.pwdInitYn === 'N') {
|
||||
setOpen(true)
|
||||
setContent(html)
|
||||
setModalProps({
|
||||
closeOnOverlayClick: false,
|
||||
closeOnEsc: false,
|
||||
showCloseIcon: false,
|
||||
})
|
||||
} else {
|
||||
fetchObjectList()
|
||||
}
|
||||
}, [sessionState])
|
||||
|
||||
const fetchObjectList = async () => {
|
||||
try {
|
||||
// const apiUrl = `/api/main-page/object/X167/list`
|
||||
const apiUrl = `/api/main-page/object/${sessionState?.storeId}/list`
|
||||
const res = await get({ url: apiUrl })
|
||||
if (res) {
|
||||
setSaleStoreId(res.saleStoreId)
|
||||
setSaleStoreName(res.saleStoreName)
|
||||
setObjectList(res.objectList)
|
||||
setBusinessCharger(res.businessCharger)
|
||||
setBusinessChargerMail(res.businessChargerMail)
|
||||
setBusinessChargerTel(res.businessChargerTel)
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('MAIN API fetching error:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// 모달팝업 컨텐츠
|
||||
const html = (
|
||||
<>
|
||||
<p className="text-2xl">비밀번호 변경</p>
|
||||
<p>* 초기화된 비밀번호로 로그인한 경우, 비밀번호를 변경해야 사이트 이용이 가능합니다.</p>
|
||||
<p>비밀번호를 변경하지 않을 경우, 로그인 화면으로 이동합니다.</p>
|
||||
<form action={updateProcess}>
|
||||
<table>
|
||||
<colgroup>
|
||||
<col style={{ width: '200px' }} />
|
||||
<col />
|
||||
</colgroup>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>
|
||||
신규비밀번호 입력 <span className="important">*</span>
|
||||
</th>
|
||||
<td>
|
||||
<div className="input-wrap" style={{ width: '500px' }}>
|
||||
<input
|
||||
type="text"
|
||||
className="input-origin"
|
||||
name="password1"
|
||||
required
|
||||
autoComplete="off"
|
||||
onChange={checkValue}
|
||||
onKeyUp={checkValue}
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>
|
||||
신규비밀번호 재입력 <span className="important">*</span>
|
||||
</th>
|
||||
<td>
|
||||
<div className="input-wrap" style={{ width: '500px' }}>
|
||||
<input
|
||||
type="text"
|
||||
className="input-origin"
|
||||
name="password2"
|
||||
required
|
||||
autoComplete="off"
|
||||
onChange={checkValue}
|
||||
onKeyUp={checkValue}
|
||||
/>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<button type="submit" className="btn-origin navy mr5">
|
||||
변경
|
||||
</button>
|
||||
<button
|
||||
className="btn-origin grey"
|
||||
onClick={() => {
|
||||
router.push('/login')
|
||||
setOpen(false)
|
||||
}}
|
||||
>
|
||||
변경안함
|
||||
</button>
|
||||
</form>
|
||||
</>
|
||||
)
|
||||
|
||||
// 엔터 이벤트
|
||||
const handleByOnKeyUp = (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
//물건번호 일떄
|
||||
if (searchRadioType === 'object') {
|
||||
setStuffSearch({
|
||||
...stuffSearch,
|
||||
schObjectNo: searchTxt,
|
||||
code: 'M',
|
||||
})
|
||||
router.push('/management/stuff')
|
||||
} else {
|
||||
alert('작업중')
|
||||
return
|
||||
|
||||
//FAQ일떄
|
||||
//faq리코일에
|
||||
//searchValue= e.target.value
|
||||
//mainFlag:'Y'
|
||||
// router.push('/community/faq')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 라디오 변경 이벤트
|
||||
const handleOnChangeRadio = (e) => {
|
||||
setSearchRadioType(e.target.value)
|
||||
}
|
||||
|
||||
// 돋보기 클릭
|
||||
const handleOnSubmit = () => {
|
||||
if (searchRadioType === 'object') {
|
||||
setStuffSearch({
|
||||
...stuffSearch,
|
||||
schObjectNo: searchTxt,
|
||||
code: 'M',
|
||||
})
|
||||
|
||||
router.push('/management/stuff')
|
||||
} else {
|
||||
alert('작업중')
|
||||
return
|
||||
//FAQ일떄
|
||||
//faq리코일에
|
||||
//searchValue= e.target.value
|
||||
//mainFlag:'Y'
|
||||
// router.push('/community/faq')
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1>Main page</h1>
|
||||
{sessionState.pwdInitYn === 'Y' && (
|
||||
<>
|
||||
<div className="background-bord"></div>
|
||||
<div className="main-contents">
|
||||
<div className="store-id-wrap">
|
||||
<div className="store-id-box">
|
||||
<div className="store-id-title">
|
||||
{getMessage('main.storeId')}/ {getMessage('main.storeName')}
|
||||
</div>
|
||||
</div>
|
||||
<span className="store-arr"></span>
|
||||
<div className="store-id-name">
|
||||
{saleStoreId} / {saleStoreName}
|
||||
</div>
|
||||
</div>
|
||||
<div className="main-search-wrap">
|
||||
<div className="search-raido-wrap">
|
||||
<div className="d-check-radio">
|
||||
<input type="radio" name="radio01" id="object" value="object" defaultChecked onChange={handleOnChangeRadio} />
|
||||
<label htmlFor="object">{getMessage('main.objectNo')}</label>
|
||||
</div>
|
||||
<div className="d-check-radio">
|
||||
<input type="radio" name="radio01" id="faq" value="faq" onChange={handleOnChangeRadio} />
|
||||
<label htmlFor="faq">{getMessage('main.faq')}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div className="search-input-box">
|
||||
<input
|
||||
type="text"
|
||||
className="main-search"
|
||||
placeholder=""
|
||||
onKeyUp={handleByOnKeyUp}
|
||||
onChange={(e) => {
|
||||
setSearchTxt(e.target.value)
|
||||
}}
|
||||
/>
|
||||
<button className="search-icon" onClick={handleOnSubmit}></button>
|
||||
</div>
|
||||
</div>
|
||||
<MainContents
|
||||
objectList={objectList}
|
||||
businessCharger={businessCharger}
|
||||
businessChargerMail={businessChargerMail}
|
||||
businessChargerTel={businessChargerTel}
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
166
src/components/main/MainContents.jsx
Normal file
166
src/components/main/MainContents.jsx
Normal file
@ -0,0 +1,166 @@
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import ProductItem from './ProductItem'
|
||||
import { useMessage } from '@/hooks/useMessage'
|
||||
import Image from 'next/image'
|
||||
import dayjs from 'dayjs'
|
||||
import { useAxios } from '@/hooks/useAxios'
|
||||
import { useRecoilValue } from 'recoil'
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { globalLocaleStore } from '@/store/localeAtom'
|
||||
import { queryStringFormatter } from '@/util/common-utils'
|
||||
export default function MainContents({ objectList, businessCharger, businessChargerMail, businessChargerTel }) {
|
||||
const { getMessage } = useMessage()
|
||||
const router = useRouter()
|
||||
const globalLocaleState = useRecoilValue(globalLocaleStore)
|
||||
const { get } = useAxios(globalLocaleState)
|
||||
|
||||
//공지사항
|
||||
const [recentNoticeList, setRecentNoticeList] = useState([])
|
||||
|
||||
//FAQ
|
||||
const [recentFaqList, setRecentFaqList] = useState([])
|
||||
|
||||
useEffect(() => {
|
||||
fetchNoticeList()
|
||||
fetchFaqList()
|
||||
}, [])
|
||||
|
||||
//공지사항 호출
|
||||
const fetchNoticeList = async () => {
|
||||
try {
|
||||
const param = {
|
||||
schNoticeTpCd: 'QC',
|
||||
schNoticeClsCd: 'NOTICE',
|
||||
startRow: 1,
|
||||
endRow: 1,
|
||||
}
|
||||
// const noticeApiUrl = `api/board/list?schNoticeTpCd=QC&schNoticeClsCd=NOTICE&schTitle=&startRow=1&endRow=1`
|
||||
const noticeApiUrl = `api/board/list?${queryStringFormatter(param)}`
|
||||
const res = await get({ url: noticeApiUrl })
|
||||
//console.log('공지res::', res)
|
||||
if (res) {
|
||||
if (res.data.length > 0) {
|
||||
setRecentNoticeList(res.data)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('NOTICE fetching error:', error)
|
||||
}
|
||||
}
|
||||
|
||||
//FAQ 호출
|
||||
const fetchFaqList = async () => {
|
||||
try {
|
||||
const param = {
|
||||
schNoticeTpCd: 'QC',
|
||||
schNoticeClsCd: 'FAQ',
|
||||
startRow: 1,
|
||||
endRow: 3,
|
||||
}
|
||||
// const faqApiUrl = `api/board/list?schNoticeTpCd=QC&schNoticeClsCd=FAQ&schTitle=&startRow=1&endRow=1`
|
||||
const faqApiUrl = `api/board/list?${queryStringFormatter(param)}`
|
||||
const res = await get({ url: faqApiUrl })
|
||||
//console.log('FAQres::', res)
|
||||
if (res) {
|
||||
if (res.data.length > 0) {
|
||||
setRecentFaqList(res.data)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('FAQ fetching error:', error)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="main-product-list-wrap">
|
||||
<div className="main-product-list">
|
||||
<ProductItem num={1} name={getMessage('main.content.objectList')}>
|
||||
<ul className="recently-list">
|
||||
{objectList?.length > 0 ? (
|
||||
<>
|
||||
{objectList.map((row) => {
|
||||
return (
|
||||
<li
|
||||
key={row.objectNo}
|
||||
className="recently-item"
|
||||
onClick={() => {
|
||||
if (row.objectNo.substring(0, 1) === 'R') {
|
||||
router.push(`/management/stuff/detail?objectNo=${row.objectNo.toString()}`)
|
||||
} else {
|
||||
router.push(`/management/stuff/tempdetail?objectNo=${row.objectNo.toString()}`)
|
||||
}
|
||||
}}
|
||||
>
|
||||
<div className="item-inner">
|
||||
<span className="time">{dayjs(row.lastEditDatetime).format('YYYY.MM.DD HH:mm:ss')}</span>
|
||||
<span>{row.objectNo}</span>
|
||||
<span>{row.objectName}</span>
|
||||
<span>{row.saleStoreName}</span>
|
||||
</div>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<li>
|
||||
<div className="item-inner">최근 갱신 물건이 없습니다</div>
|
||||
</li>
|
||||
</>
|
||||
)}
|
||||
</ul>
|
||||
</ProductItem>
|
||||
<ProductItem num={2} name={getMessage('main.content.notice')}>
|
||||
<div className="notice-box">
|
||||
<div className="notice-day pre">{dayjs(recentNoticeList[0]?.regDt).format('YYYY.MM.DD')}</div>
|
||||
<div className="notice-title">{recentNoticeList[0]?.title}</div>
|
||||
<div className="notice-contents">{recentNoticeList[0]?.contents}</div>
|
||||
</div>
|
||||
</ProductItem>
|
||||
</div>
|
||||
<div className="main-product-list">
|
||||
<ProductItem num={3} name={getMessage('main.faq')}>
|
||||
<ul className="faq-list">
|
||||
{recentFaqList.map((row, index) => {
|
||||
return (
|
||||
<li key={row.rowNumber} className="faq-item">
|
||||
<div className="faq-item-inner">
|
||||
<div className="faq-num pre">FAQ {row.noticeNo}</div>
|
||||
<div className="faq-title pre">{row.title}</div>
|
||||
<div className="faq-day pre">{dayjs(row.regDt).format('YYYY.MM.DD')}</div>
|
||||
</div>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</ProductItem>
|
||||
<ProductItem num={4} name={'Data Download'}>
|
||||
<div className="data-download-wrap">
|
||||
<button className="data-down">
|
||||
<span>{getMessage('main.content.download1')}</span>
|
||||
</button>
|
||||
<button className="data-down">
|
||||
<span>{getMessage('main.content.download2')}</span>
|
||||
</button>
|
||||
</div>
|
||||
</ProductItem>
|
||||
<ProductItem num={5} name={'Sales Contact info'}>
|
||||
<ul className="contact-info-list">
|
||||
<li className="info-item">
|
||||
<div className="icon-box">
|
||||
<Image src="/static/images/main/user.svg" alt="react" width={20} height={20} />
|
||||
</div>
|
||||
<div className="infor-data">{businessCharger}</div>
|
||||
</li>
|
||||
<li className="info-item">
|
||||
<div className="icon-box">
|
||||
<Image src="/static/images/main/mail.svg" alt="react" width={20} height={20} />
|
||||
</div>
|
||||
<div className="infor-data pre">{businessChargerMail}</div>
|
||||
</li>
|
||||
</ul>
|
||||
</ProductItem>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
35
src/components/main/ProductItem.jsx
Normal file
35
src/components/main/ProductItem.jsx
Normal file
@ -0,0 +1,35 @@
|
||||
import React from 'react'
|
||||
import { useRouter } from 'next/navigation'
|
||||
export default function ProductItem({ num, name, children }) {
|
||||
const router = useRouter()
|
||||
|
||||
// 더보기 페이지 이동
|
||||
const pageMove = (num) => {
|
||||
if (num === 1) {
|
||||
router.push('/management/stuff')
|
||||
} else if (num === 2) {
|
||||
router.push('/community/notice')
|
||||
} else {
|
||||
router.push('/community/faq')
|
||||
}
|
||||
}
|
||||
return (
|
||||
<div className={`product-item item0${num}`}>
|
||||
<div className="product-item-title-wrap">
|
||||
<h2 className="product-item-title">
|
||||
<span className={`item-logo ico0${num}`}></span>
|
||||
{name}
|
||||
</h2>
|
||||
{num !== 4 && num !== 5 && (
|
||||
<button
|
||||
className="more-btn"
|
||||
onClick={() => {
|
||||
pageMove(num)
|
||||
}}
|
||||
></button>
|
||||
)}
|
||||
</div>
|
||||
<div className="product-item-content">{children}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@ -278,6 +278,54 @@ export default function Stuff() {
|
||||
// const apiUrl = `/api/object/list?saleStoreId=201TES01&${queryStringFormatter(params)}`
|
||||
// const apiUrl = `/api/object/list?saleStoreId=X167&${queryStringFormatter(params)}`
|
||||
const apiUrl = `/api/object/list?saleStoreId=${sessionState?.storeId}&${queryStringFormatter(params)}`
|
||||
|
||||
await get({
|
||||
url: apiUrl,
|
||||
}).then((res) => {
|
||||
if (!isEmptyArray(res)) {
|
||||
setGridProps({ ...gridProps, gridData: res, count: res[0].totCnt })
|
||||
setGridCount(res[0].totCnt)
|
||||
}
|
||||
})
|
||||
}
|
||||
fetchData()
|
||||
} else {
|
||||
const params = {
|
||||
schObjectNo: '',
|
||||
schAddress: '',
|
||||
schObjectName: '',
|
||||
schSaleStoreName: '',
|
||||
schReceiveUser: '',
|
||||
schDispCompanyName: '',
|
||||
schDateType: 'U',
|
||||
schFromDt: dayjs(new Date()).add(-1, 'year').format('YYYY-MM-DD'),
|
||||
schToDt: dayjs(new Date()).format('YYYY-MM-DD'),
|
||||
startRow: (curPage - 1) * defaultSize + 1,
|
||||
endRow: curPage * defaultSize,
|
||||
schSelSaleStoreId: '',
|
||||
schSortType: 'R',
|
||||
}
|
||||
|
||||
async function fetchData() {
|
||||
//api에 넘길값 startRow, endRow
|
||||
// let startRow
|
||||
// let endRow
|
||||
// startRow = (curPage - 1) * size + 1
|
||||
// endRow = curPage * size
|
||||
// console.log('startrow::', startRow)
|
||||
// console.log('endRow::', endRow)
|
||||
|
||||
// let curPage
|
||||
// let totalpage
|
||||
// let totalCount
|
||||
// let size
|
||||
// let pageCount
|
||||
|
||||
// console.log('화면진입 세션정보::::::::::', sessionState)
|
||||
// const apiUrl = `/api/object/list?saleStoreId=201TES01&${queryStringFormatter(params)}`
|
||||
// const apiUrl = `/api/object/list?saleStoreId=X167&${queryStringFormatter(params)}`
|
||||
const apiUrl = `/api/object/list?saleStoreId=${sessionState?.storeId}&${queryStringFormatter(params)}`
|
||||
|
||||
await get({
|
||||
url: apiUrl,
|
||||
}).then((res) => {
|
||||
@ -294,10 +342,10 @@ export default function Stuff() {
|
||||
|
||||
useEffect(() => {
|
||||
if (stuffSearchParams?.code === 'E') {
|
||||
//console.log('조회누름::::::::', stuffSearchParams)
|
||||
stuffSearchParams.startRow = (curPage - 1) * defaultSize + 1
|
||||
stuffSearchParams.endRow = curPage * defaultSize
|
||||
stuffSearchParams.schSortType = defaultSortType
|
||||
// console.log('조회누름::::::::', stuffSearchParams)
|
||||
async function fetchData() {
|
||||
// console.log('조회누름 세션정보:::::::::::::', sessionState)
|
||||
// const apiUrl = `/api/object/list?saleStoreId=201TES01&${queryStringFormatter(stuffSearchParams)}`
|
||||
@ -378,6 +426,7 @@ export default function Stuff() {
|
||||
setAppMessageState(JA)
|
||||
}
|
||||
}, [globalLocaleState])
|
||||
|
||||
return (
|
||||
<>
|
||||
{/* 퍼블시작 */}
|
||||
|
||||
@ -182,7 +182,7 @@ export default function StuffSearchCondition() {
|
||||
type="text"
|
||||
className="input-light"
|
||||
placeholder="물건번호 입력"
|
||||
value={stuffSearch?.code === 'E' ? stuffSearch.schObjectNo : objectNo}
|
||||
value={stuffSearch?.code === 'E' || stuffSearch?.code === 'M' ? stuffSearch.schObjectNo : objectNo}
|
||||
onChange={(e) => {
|
||||
setObjectNo(e.target.value)
|
||||
setStuffSearch({ ...stuffSearch, code: 'S', schObjectNo: e.target.value })
|
||||
@ -255,9 +255,6 @@ export default function StuffSearchCondition() {
|
||||
</td>
|
||||
<th>판매대리점 선택</th>
|
||||
<td>
|
||||
{/* <div className="select-wrap">
|
||||
<select className="select-light" name="" id=""></select>
|
||||
</div> */}
|
||||
{schSelSaleStoreList?.length > 0 && (
|
||||
<Select
|
||||
options={schSelSaleStoreList}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user