feat(提现申请功能): 对接接口完成
This commit is contained in:
parent
bb04c3c6db
commit
0aeea9dd20
14
src/api/upload.js
Normal file
14
src/api/upload.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { get, post } from '../utils/http.js'
|
||||||
|
|
||||||
|
// 上传图片
|
||||||
|
export const uploadImg = async (file) => {
|
||||||
|
try {
|
||||||
|
const formData = new FormData()
|
||||||
|
formData.append('file', file) // 这里的'file'需要与后端接口期望的参数名一致
|
||||||
|
const response = await post('/external/oss/upload', formData)
|
||||||
|
return response
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Failed to upload img:', error)
|
||||||
|
throw error
|
||||||
|
}
|
||||||
|
}
|
||||||
39
src/utils/applyStore.js
Normal file
39
src/utils/applyStore.js
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
let applyInfo = null
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置申请资料缓存
|
||||||
|
* @param {Object} applyInfo - 申请资料
|
||||||
|
*/
|
||||||
|
export function setApplyInfo(data) {
|
||||||
|
applyInfo = data
|
||||||
|
|
||||||
|
// 可选:存储到localStorage以便持久化
|
||||||
|
if (data) {
|
||||||
|
localStorage.setItem('applyInfo', JSON.stringify(data))
|
||||||
|
} else {
|
||||||
|
localStorage.removeItem('applyInfo')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取申请资料
|
||||||
|
* @returns {Object|null}
|
||||||
|
*/
|
||||||
|
export function getApplyInfo() {
|
||||||
|
if (applyInfo) {
|
||||||
|
return applyInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从localStorage恢复
|
||||||
|
const stored = localStorage.getItem('applyInfo')
|
||||||
|
if (stored) {
|
||||||
|
try {
|
||||||
|
applyInfo = JSON.parse(stored)
|
||||||
|
return applyInfo
|
||||||
|
} catch (error) {
|
||||||
|
console.error('解析申请资料缓存失败:', error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
@ -1,5 +1,5 @@
|
|||||||
// HTTP请求配置
|
// HTTP请求配置
|
||||||
import {getApiBaseUrl, getCurrentEnv, getUserAuth, isDebugMode} from './env.js'
|
import { getApiBaseUrl, getCurrentEnv, getUserAuth, isDebugMode } from './env.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 自定义API错误类
|
* 自定义API错误类
|
||||||
@ -8,14 +8,14 @@ class ApiError extends Error {
|
|||||||
constructor(message, options = {}) {
|
constructor(message, options = {}) {
|
||||||
super(message)
|
super(message)
|
||||||
this.name = 'ApiError'
|
this.name = 'ApiError'
|
||||||
this.status = options.status || null // HTTP状态码
|
this.status = options.status || null // HTTP状态码
|
||||||
this.errorCode = options.errorCode || null // 业务错误码 (如: 6010)
|
this.errorCode = options.errorCode || null // 业务错误码 (如: 6010)
|
||||||
this.errorCodeName = options.errorCodeName || null // 错误码名称 (如: "TEAM_NOT_FOUND")
|
this.errorCodeName = options.errorCodeName || null // 错误码名称 (如: "TEAM_NOT_FOUND")
|
||||||
this.errorMsg = options.errorMsg || null // 业务错误消息
|
this.errorMsg = options.errorMsg || null // 业务错误消息
|
||||||
this.time = options.time || null // API响应时间戳
|
this.time = options.time || null // API响应时间戳
|
||||||
this.extValues = options.extValues || null // 扩展值
|
this.extValues = options.extValues || null // 扩展值
|
||||||
this.response = options.response || null // 原始响应数据
|
this.response = options.response || null // 原始响应数据
|
||||||
this.type = options.type || 'unknown' // 错误类型: 'http' | 'business' | 'network'
|
this.type = options.type || 'unknown' // 错误类型: 'http' | 'business' | 'network'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,7 +32,7 @@ if (DEBUG_MODE) {
|
|||||||
const token = getUserAuth()
|
const token = getUserAuth()
|
||||||
// 默认公共请求头配置
|
// 默认公共请求头配置
|
||||||
let COMMON_HEADERS = {
|
let COMMON_HEADERS = {
|
||||||
'authorization': `Bearer ${token}`,
|
authorization: `Bearer ${token}`,
|
||||||
'req-app-intel': 'version=1.0.0;build=1;model=SM-G9550;sysVersion=9;channel=Google',
|
'req-app-intel': 'version=1.0.0;build=1;model=SM-G9550;sysVersion=9;channel=Google',
|
||||||
'req-client': 'Android',
|
'req-client': 'Android',
|
||||||
'req-imei': '8fa54d728ab449e04f9292329ed44bda',
|
'req-imei': '8fa54d728ab449e04f9292329ed44bda',
|
||||||
@ -49,7 +49,7 @@ let COMMON_HEADERS = {
|
|||||||
export function updateCommonHeaders(headers) {
|
export function updateCommonHeaders(headers) {
|
||||||
COMMON_HEADERS = {
|
COMMON_HEADERS = {
|
||||||
...COMMON_HEADERS,
|
...COMMON_HEADERS,
|
||||||
...headers
|
...headers,
|
||||||
}
|
}
|
||||||
console.debug('🔄 Updated common headers:', COMMON_HEADERS)
|
console.debug('🔄 Updated common headers:', COMMON_HEADERS)
|
||||||
}
|
}
|
||||||
@ -110,37 +110,40 @@ export { COMMON_HEADERS, ApiError }
|
|||||||
* @returns {Promise} - 返回Promise对象
|
* @returns {Promise} - 返回Promise对象
|
||||||
*/
|
*/
|
||||||
export async function request(url, options = {}) {
|
export async function request(url, options = {}) {
|
||||||
const {
|
const { method = 'GET', data = null, headers = {}, ...otherOptions } = options
|
||||||
method = 'GET',
|
|
||||||
data = null,
|
|
||||||
headers = {},
|
|
||||||
...otherOptions
|
|
||||||
} = options
|
|
||||||
|
|
||||||
const fullUrl = url.startsWith('http') ? url : `${API_BASE_URL}${url}`
|
const fullUrl = url.startsWith('http') ? url : `${API_BASE_URL}${url}`
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
method,
|
method,
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
|
||||||
...COMMON_HEADERS,
|
...COMMON_HEADERS,
|
||||||
...headers
|
...headers,
|
||||||
},
|
},
|
||||||
...otherOptions
|
...otherOptions,
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果是POST、PUT等方法且有数据,添加body
|
// 如果是POST、PUT等方法且有数据,添加body
|
||||||
if (data && ['POST', 'PUT', 'PATCH'].includes(method.toUpperCase())) {
|
if (data && ['POST', 'PUT', 'PATCH'].includes(method.toUpperCase())) {
|
||||||
config.body = JSON.stringify(data)
|
if (data instanceof FormData) {
|
||||||
|
// FormData类型:由浏览器自动设置Content-Type
|
||||||
|
config.body = data
|
||||||
|
// 删除手动设置的Content-Type(重要!)
|
||||||
|
if (config.headers['Content-Type']) {
|
||||||
|
delete config.headers['Content-Type']
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 非FormData:按JSON处理
|
||||||
|
config.body = JSON.stringify(data)
|
||||||
|
config.headers['Content-Type'] = 'application/json'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
const response = await fetch(fullUrl, config)
|
const response = await fetch(fullUrl, config)
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
// 尝试解析响应体获取更多错误信息
|
// 尝试解析响应体获取更多错误信息
|
||||||
let errorData = await response.json()
|
let errorData = await response.json()
|
||||||
|
|
||||||
throw new ApiError(`HTTP Error: ${response.status} ${response.statusText}`, {
|
throw new ApiError(`HTTP Error: ${response.status} ${response.statusText}`, {
|
||||||
type: 'http',
|
type: 'http',
|
||||||
@ -149,7 +152,7 @@ export async function request(url, options = {}) {
|
|||||||
errorCode: errorData.errorCode,
|
errorCode: errorData.errorCode,
|
||||||
errorCodeName: errorData.errorCodeName,
|
errorCodeName: errorData.errorCodeName,
|
||||||
errorMsg: errorData.errorMsg,
|
errorMsg: errorData.errorMsg,
|
||||||
time: errorData.time
|
time: errorData.time,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,7 +183,7 @@ export async function request(url, options = {}) {
|
|||||||
throw new ApiError(error.message || 'Network or unknown error', {
|
throw new ApiError(error.message || 'Network or unknown error', {
|
||||||
type: 'network',
|
type: 'network',
|
||||||
originalError: error,
|
originalError: error,
|
||||||
code: 'NETWORK_ERROR'
|
code: 'NETWORK_ERROR',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1399,15 +1399,19 @@ const getWinners = async () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//获取中奖记录
|
//获取领取任务奖励
|
||||||
const receiveTaskReward = async (code) => {
|
const receiveTaskReward = async (code) => {
|
||||||
let data = {
|
let data = {
|
||||||
taskCode: code,
|
taskCode: code,
|
||||||
}
|
}
|
||||||
const resReceive = await receiveTickets(data)
|
try {
|
||||||
if (resReceive.status && resReceive.body) {
|
const resReceive = await receiveTickets(data)
|
||||||
getTaskList() //获取任务列表
|
if (resReceive.status && resReceive.body) {
|
||||||
getTickets() //获取拥有的抽奖券
|
getTaskList() //获取任务列表
|
||||||
|
getTickets() //获取拥有的抽奖券
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
showError(error.errorMsg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -70,7 +70,9 @@
|
|||||||
<div v-else style="width: 100%; display: flex; flex-direction: column; gap: 8px">
|
<div v-else style="width: 100%; display: flex; flex-direction: column; gap: 8px">
|
||||||
<!-- 图片 -->
|
<!-- 图片 -->
|
||||||
<div style="display: flex; justify-content: space-between; align-items: center">
|
<div style="display: flex; justify-content: space-between; align-items: center">
|
||||||
<span style="font-weight: 510; font-size: 1em">Passport/ID card:(3/3)</span>
|
<span style="font-weight: 510; font-size: 1em"
|
||||||
|
>Passport/ID card:({{ bankCardInfo.cardImages.length }}/3)</span
|
||||||
|
>
|
||||||
<img
|
<img
|
||||||
src="../../../assets/icon/arrow.png"
|
src="../../../assets/icon/arrow.png"
|
||||||
alt=""
|
alt=""
|
||||||
@ -79,28 +81,24 @@
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div style="display: flex; align-items: center; gap: 4px">
|
<div style="display: flex; align-items: center; gap: 4px">
|
||||||
<div
|
<img
|
||||||
style="width: 10vw; aspect-ratio: 1/1; border-radius: 4px; background: #ededed"
|
:src="imgUrl"
|
||||||
></div>
|
alt=""
|
||||||
<div
|
v-for="imgUrl in bankCardInfo.cardImages"
|
||||||
style="width: 10vw; aspect-ratio: 1/1; border-radius: 4px; background: #ededed"
|
style="width: 10vw; aspect-ratio: 1/1; border-radius: 4px"
|
||||||
></div>
|
/>
|
||||||
<div
|
|
||||||
style="width: 10vw; aspect-ratio: 1/1; border-radius: 4px; background: #ededed"
|
|
||||||
></div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 卡号 -->
|
<!-- 卡号 -->
|
||||||
<div style="font-weight: 500">Contact number:</div>
|
<div style="font-weight: 500">Contact number:</div>
|
||||||
<div style="color: rgba(0, 0, 0, 0.4); font-weight: 600; font-size: 0.8em">
|
<div style="color: rgba(0, 0, 0, 0.4); font-weight: 600; font-size: 0.8em">
|
||||||
123-456-789-1234
|
{{ bankCardInfo.contactNumber }}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 备注信息 -->
|
<!-- 备注信息 -->
|
||||||
<div style="font-weight: 500">Other description:</div>
|
<div style="font-weight: 500">Other description:</div>
|
||||||
<div style="color: rgba(0, 0, 0, 0.4); font-weight: 600; font-size: 0.8em">
|
<div style="color: rgba(0, 0, 0, 0.4); font-weight: 600; font-size: 0.8em">
|
||||||
asdfasdfasdfasdf asfasfsaasdasdfasdfasdfa sfasfasfasfasfsdasdfasdfasf asdfafdsa asdf
|
{{ bankCardInfo.description }}
|
||||||
asdasddsasdsadads
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -192,7 +190,9 @@ import GeneralHeader from '@/components/GeneralHeader.vue'
|
|||||||
import maskLayer from '@/components/MaskLayer.vue'
|
import maskLayer from '@/components/MaskLayer.vue'
|
||||||
import {
|
import {
|
||||||
withdrawableAmount, //获取可提现金额
|
withdrawableAmount, //获取可提现金额
|
||||||
|
withdrawApply, //提交提现申请
|
||||||
} from '@/api/lottery'
|
} from '@/api/lottery'
|
||||||
|
import { setApplyInfo, getApplyInfo } from '@/utils/applyStore.js'
|
||||||
import { ref, onMounted, computed } from 'vue'
|
import { ref, onMounted, computed } from 'vue'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
@ -200,9 +200,7 @@ const router = useRouter()
|
|||||||
const maskLayerShow = ref(false)
|
const maskLayerShow = ref(false)
|
||||||
const currentAmount = ref(0) //可提现金额
|
const currentAmount = ref(0) //可提现金额
|
||||||
|
|
||||||
const bankCardInfo = ref({
|
const bankCardInfo = ref({})
|
||||||
// id: '',
|
|
||||||
})
|
|
||||||
const hasBankCard = computed(() => {
|
const hasBankCard = computed(() => {
|
||||||
return Object.keys(bankCardInfo.value).length > 0
|
return Object.keys(bankCardInfo.value).length > 0
|
||||||
})
|
})
|
||||||
@ -220,6 +218,15 @@ const gotoKYC = () => {
|
|||||||
// 提现按钮
|
// 提现按钮
|
||||||
const CashOut = () => {
|
const CashOut = () => {
|
||||||
if (hasBankCard.value) {
|
if (hasBankCard.value) {
|
||||||
|
try {
|
||||||
|
const resApply = withdrawApply(getApplyInfo())
|
||||||
|
if (resApply.status) {
|
||||||
|
setApplyInfo()
|
||||||
|
router.go(-1)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
showError(error.errorMsg)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
maskLayerShow.value = true
|
maskLayerShow.value = true
|
||||||
}
|
}
|
||||||
@ -242,6 +249,8 @@ const getDrawableAmount = async () => {
|
|||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getDrawableAmount() //获取可提现金额
|
getDrawableAmount() //获取可提现金额
|
||||||
|
bankCardInfo.value =
|
||||||
|
{ ...getApplyInfo(), cardImages: JSON.parse(getApplyInfo().cardImages) } || {}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
@ -61,19 +61,27 @@
|
|||||||
<!-- 上传图片 -->
|
<!-- 上传图片 -->
|
||||||
<div style="font-weight: 600">Passport/lD card:</div>
|
<div style="font-weight: 600">Passport/lD card:</div>
|
||||||
<div style="display: flex; align-items: center">
|
<div style="display: flex; align-items: center">
|
||||||
<img
|
<div style="position: relative">
|
||||||
src="/src/assets/icon/addImg.png"
|
<img
|
||||||
alt=""
|
src="/src/assets/icon/addImg.png"
|
||||||
style="display: block; width: 15vw; aspect-ratio: 1/1; object-fit: cover"
|
alt=""
|
||||||
/>
|
style="display: block; width: 15vw; aspect-ratio: 1/1; object-fit: cover"
|
||||||
|
/>
|
||||||
|
<input
|
||||||
|
type="file"
|
||||||
|
@change="handleFileChange"
|
||||||
|
style="position: absolute; inset: 0; opacity: 0"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<img
|
<img
|
||||||
src="/src/assets/icon/direction.png"
|
src="/src/assets/icon/direction.png"
|
||||||
alt=""
|
alt=""
|
||||||
style="display: block; width: 5vw; aspect-ratio: 1/1; object-fit: cover"
|
style="display: block; width: 5vw; aspect-ratio: 1/1; object-fit: cover"
|
||||||
/>
|
/>
|
||||||
<div style="position: relative">
|
<div v-for="(imgUrl, index) in previewUrls" style="position: relative">
|
||||||
<img
|
<img
|
||||||
src="/src/assets/icon/addImg.png"
|
:src="imgUrl"
|
||||||
alt=""
|
alt=""
|
||||||
style="display: block; width: 15vw; aspect-ratio: 1/1; object-fit: cover"
|
style="display: block; width: 15vw; aspect-ratio: 1/1; object-fit: cover"
|
||||||
/>
|
/>
|
||||||
@ -89,6 +97,7 @@
|
|||||||
aspect-ratio: 1/1;
|
aspect-ratio: 1/1;
|
||||||
object-fit: cover;
|
object-fit: cover;
|
||||||
"
|
"
|
||||||
|
@click="handleCancel(index)"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -119,11 +128,10 @@
|
|||||||
<script setup>
|
<script setup>
|
||||||
import GeneralHeader from '@/components/GeneralHeader.vue'
|
import GeneralHeader from '@/components/GeneralHeader.vue'
|
||||||
import { ref, onMounted, computed } from 'vue'
|
import { ref, onMounted, computed } from 'vue'
|
||||||
import {
|
|
||||||
withdrawApply, //获取可提现金额
|
|
||||||
} from '@/api/lottery'
|
|
||||||
import { showError, showWarning, showInfo, showSuccess } from '@/utils/toast.js'
|
import { showError, showWarning, showInfo, showSuccess } from '@/utils/toast.js'
|
||||||
|
import { setApplyInfo, getApplyInfo } from '@/utils/applyStore.js'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
|
import { uploadImg } from '@/api/upload.js'
|
||||||
|
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
|
||||||
@ -131,22 +139,77 @@ const cardNum = ref('')
|
|||||||
const description = ref('')
|
const description = ref('')
|
||||||
const count = computed(() => description.value.length)
|
const count = computed(() => description.value.length)
|
||||||
|
|
||||||
|
const previewUrls = ref([]) //预览图
|
||||||
|
const tempFiles = ref([]) //用于提交图片的临时文件列表
|
||||||
|
const resImgUrls = ref([]) //用于提交申请的文件数组
|
||||||
|
|
||||||
|
// 点击上传图片
|
||||||
|
// 处理文件选择
|
||||||
|
const handleFileChange = (e) => {
|
||||||
|
//最多3张图片
|
||||||
|
if (previewUrls.value.length < 3) {
|
||||||
|
const fileList = e.target.files // 获取第一个文件对象
|
||||||
|
|
||||||
|
// 1. 检查是否有文件
|
||||||
|
if (!fileList || fileList.length === 0) {
|
||||||
|
console.error('未选择文件')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 提取第一个文件并验证
|
||||||
|
const file = fileList[0]
|
||||||
|
if (!file.type || !file.type.startsWith('image/')) {
|
||||||
|
console.error(`不支持的文件类型: ${file.type || '未知'}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 通过验证后的操作(示例)
|
||||||
|
console.log('文件名:', file.name) // defaultAvatar.png
|
||||||
|
console.log('文件类型:', file.type) // image/png
|
||||||
|
console.log('文件大小:', (file.size / 1024).toFixed(2), 'KB')
|
||||||
|
|
||||||
|
// 生成预览图
|
||||||
|
previewUrls.value.push(URL.createObjectURL(file))
|
||||||
|
//临时文件列表,用于上传
|
||||||
|
tempFiles.value.push(file)
|
||||||
|
// uploadImgUrl()
|
||||||
|
} else {
|
||||||
|
showWarning('you can upload up to 3 pictures at most')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCancel = (index) => {
|
||||||
|
previewUrls.value.splice(index, 1)
|
||||||
|
tempFiles.value.splice(index, 1)
|
||||||
|
resImgUrls.value.splice(index, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 上传图片
|
||||||
|
const uploadImgUrl = async () => {
|
||||||
|
for (let index = 0; index < tempFiles.value.length; index++) {
|
||||||
|
const file = tempFiles.value[index]
|
||||||
|
try {
|
||||||
|
const res = await uploadImg(file)
|
||||||
|
if (res.status) {
|
||||||
|
resImgUrls.value.push(res.body)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
showError(error.errorMsg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 提交申请
|
// 提交申请
|
||||||
const Submit = () => {
|
const Submit = async () => {
|
||||||
|
await uploadImgUrl()
|
||||||
let data = {
|
let data = {
|
||||||
amount: 10,
|
amount: 10,
|
||||||
contactNumber: cardNum.value,
|
contactNumber: cardNum.value,
|
||||||
description: description.value,
|
description: description.value,
|
||||||
cardImages: '',
|
cardImages: JSON.stringify(resImgUrls.value),
|
||||||
}
|
|
||||||
try {
|
|
||||||
const resApply = withdrawApply(data)
|
|
||||||
if (resApply.status) {
|
|
||||||
router.go(-1)
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
showError(error.errorMsg)
|
|
||||||
}
|
}
|
||||||
|
setApplyInfo(data)
|
||||||
|
router.go(-1)
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user