This is an automated email from the ASF dual-hosted git repository. shuai pushed a commit to branch feat/1.6.1/md in repository https://gitbox.apache.org/repos/asf/answer.git
commit 17e1b301752cd136e98cf83e81bae89cd03a3007 Author: shuai <lishuail...@sifou.com> AuthorDate: Thu Jun 26 16:44:42 2025 +0800 feat: Blocked users can choose the duration of the block #1361 --- i18n/en_US.yaml | 12 +++ i18n/zh_CN.yaml | 12 +++ ui/src/common/constants.ts | 30 +++++--- ui/src/components/Header/index.tsx | 1 - .../pages/Admin/Users/components/Action/index.tsx | 16 ++-- .../Users/components/SuspenseUserModal/index.tsx | 90 ++++++++++++++++++++++ ui/src/pages/Admin/Users/index.tsx | 13 +++- 7 files changed, 150 insertions(+), 24 deletions(-) diff --git a/i18n/en_US.yaml b/i18n/en_US.yaml index cb05e5e1..3889a12a 100644 --- a/i18n/en_US.yaml +++ b/i18n/en_US.yaml @@ -1070,6 +1070,9 @@ ui: day: day hours: hours days: days + month: month + months: months + year: year reaction: heart: heart smile: smile @@ -1930,6 +1933,7 @@ ui: created_at: Created time delete_at: Deleted time suspend_at: Suspended time + suspend_until: Suspend until status: Status role: Role action: Action @@ -1964,6 +1968,8 @@ ui: suspend_user: title: Suspend this user content: A suspended user can't log in. + label: How long will the user be suspended for? + forever: Forever questions: page_title: Questions unlisted: Unlisted @@ -2024,6 +2030,12 @@ ui: label: Timezone msg: Timezone cannot be empty. text: Choose a city in the same timezone as you. + avatar: + label: Default avatar + text: For users without a custom avatar of their own. + gravatar_base_url: + label: Gravatar base URL + text: URL of the Gravatar provider's API base. Ignored when empty. smtp: page_title: SMTP from_email: diff --git a/i18n/zh_CN.yaml b/i18n/zh_CN.yaml index 9852ba60..ce5fbb07 100644 --- a/i18n/zh_CN.yaml +++ b/i18n/zh_CN.yaml @@ -1056,6 +1056,9 @@ ui: day: 天 hours: 小时 days: 日 + month: 月 + months: 月 + year: 年 reaction: heart: 爱心 smile: 微笑 @@ -1890,6 +1893,7 @@ ui: created_at: 创建时间 delete_at: 删除时间 suspend_at: 封禁时间 + suspend_until: 封禁到期 status: 状态 role: 角色 action: 操作 @@ -1924,6 +1928,8 @@ ui: suspend_user: title: 挂起此用户 content: 被封禁的用户将无法登录。 + label: 用户将被封禁多长时间? + forever: 永久 questions: page_title: 问题 unlisted: 已隐藏 @@ -1984,6 +1990,12 @@ ui: label: 时区 msg: 时区不能为空。 text: 选择一个与您相同时区的城市。 + avatar: + label: 默认头像 + text: 没有自定义头像的用户。 + gravatar_base_url: + label: Gravatar 根路径 URL + text: Gravatar 提供商的 API 基础的 URL。当为空时忽略。 smtp: page_title: SMTP from_email: diff --git a/ui/src/common/constants.ts b/ui/src/common/constants.ts index 7ca57400..18f25114 100644 --- a/ui/src/common/constants.ts +++ b/ui/src/common/constants.ts @@ -663,42 +663,52 @@ export const DEFAULT_THEME_COLOR = '#0033ff'; export const SUSPENSE_USER_TIME = [ { label: 'hours', - value: '24', + time: '24', + value: '24h', }, { label: 'hours', - value: '48', + time: '48', + value: '48h', }, { label: 'hours', - value: '72', + time: '72', + value: '72h', }, { label: 'days', - value: '7', + time: '7', + value: '7d', }, { label: 'days', - value: '14', + time: '14', + value: '14d', }, { label: 'months', - value: '1', + time: '1', + value: '1m', }, { label: 'months', - value: '2', + time: '2', + value: '2m', }, { label: 'months', - value: '3', + time: '3', + value: '3m', }, { label: 'months', - value: '6', + time: '6', + value: '6m', }, { label: 'year', - value: '1', + time: '1', + value: '1y', }, ]; diff --git a/ui/src/components/Header/index.tsx b/ui/src/components/Header/index.tsx index 0b015ba6..dbc7565d 100644 --- a/ui/src/components/Header/index.tsx +++ b/ui/src/components/Header/index.tsx @@ -87,7 +87,6 @@ const Header: FC = () => { if (theme_config?.[theme]?.navbar_style) { // const color = theme_config[theme].navbar_style.startsWith('#') themeMode = isLight(theme_config[theme].navbar_style) ? 'light' : 'dark'; - console.log('isLightTheme', themeMode); navbarStyle = `theme-${themeMode}`; } diff --git a/ui/src/pages/Admin/Users/components/Action/index.tsx b/ui/src/pages/Admin/Users/components/Action/index.tsx index 9f0847a2..b55971f4 100644 --- a/ui/src/pages/Admin/Users/components/Action/index.tsx +++ b/ui/src/pages/Admin/Users/components/Action/index.tsx @@ -42,6 +42,7 @@ interface Props { currentUser; refreshUsers: () => void; showDeleteModal: (val) => void; + showSuspenseModal: (val) => void; userData; } @@ -53,6 +54,7 @@ const UserOperation = ({ refreshUsers, showDeleteModal, userData, + showSuspenseModal, }: Props) => { const { t } = useTranslation('translation', { keyPrefix: 'admin.users' }); const Toast = useToast(); @@ -170,17 +172,9 @@ const UserOperation = ({ if (type === 'suspend') { // cons - Modal.confirm({ - title: t('suspend_user.title'), - content: t('suspend_user.content'), - cancelBtnVariant: 'link', - cancelText: t('cancel', { keyPrefix: 'btns' }), - confirmBtnVariant: 'danger', - confirmText: t('suspend', { keyPrefix: 'btns' }), - onConfirm: () => { - // active -> suspended - postUserStatus('suspended'); - }, + showSuspenseModal({ + show: true, + userId: userData.user_id, }); } diff --git a/ui/src/pages/Admin/Users/components/SuspenseUserModal/index.tsx b/ui/src/pages/Admin/Users/components/SuspenseUserModal/index.tsx new file mode 100644 index 00000000..dc59ff34 --- /dev/null +++ b/ui/src/pages/Admin/Users/components/SuspenseUserModal/index.tsx @@ -0,0 +1,90 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { useState } from 'react'; +import { Modal, Button, Form } from 'react-bootstrap'; +import { useTranslation } from 'react-i18next'; + +import { changeUserStatus } from '@/services'; +import { SUSPENSE_USER_TIME } from '@/common/constants'; +import { toastStore } from '@/stores'; + +const SuspenseUserModal = ({ show, userId, onClose, refreshUsers }) => { + const { t } = useTranslation('translation', { keyPrefix: 'admin.users' }); + const [checkVal, setCheckVal] = useState('forever'); + + const handleClose = () => { + onClose(); + setCheckVal('forever'); + }; + + const handleSubmit = (e) => { + e.preventDefault(); + changeUserStatus({ + user_id: userId, + status: 'suspended', + suspend_duration: checkVal, + }).then(() => { + toastStore.getState().show({ + msg: t('user_suspended', { keyPrefix: 'messages' }), + variant: 'success', + }); + refreshUsers?.(); + handleClose(); + }); + }; + + return ( + <Modal show={show} onHide={handleClose}> + <Modal.Header closeButton> + <Modal.Title>{t('suspend_user.title')}</Modal.Title> + </Modal.Header> + <Modal.Body> + <p>{t('suspend_user.content')}</p> + <Form> + <Form.Group controlId="delete_user" className="mb-3"> + <Form.Label>{t('suspend_user.label')}</Form.Label> + <Form.Select + value={checkVal} + onChange={(e) => setCheckVal(e.target.value)}> + <option value="forever">{t('suspend_user.forever')}</option> + {SUSPENSE_USER_TIME.map((item) => { + return ( + <option key={item.value} value={item.value}> + {item.time} {t(item.label, { keyPrefix: 'dates' })} + </option> + ); + })} + </Form.Select> + </Form.Group> + </Form> + </Modal.Body> + <Modal.Footer> + <Button variant="link" onClick={handleClose}> + {t('cancel', { keyPrefix: 'btns' })} + </Button> + <Button variant="danger" onClick={handleSubmit}> + {t('suspend', { keyPrefix: 'btns' })} + </Button> + </Modal.Footer> + </Modal> + ); +}; + +export default SuspenseUserModal; diff --git a/ui/src/pages/Admin/Users/index.tsx b/ui/src/pages/Admin/Users/index.tsx index 295cb075..7e8e4fd6 100644 --- a/ui/src/pages/Admin/Users/index.tsx +++ b/ui/src/pages/Admin/Users/index.tsx @@ -23,6 +23,7 @@ import { useSearchParams } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; import classNames from 'classnames'; +import dayjs from 'dayjs'; import { Pagination, @@ -297,7 +298,14 @@ const Users: FC = () => { <FormatTime time={user.suspended_at} /> </td> <td className="text-nowrap"> - <FormatTime time={user.suspended_at} /> + {user.suspended_until <= 0 || + Number( + dayjs(user.suspended_until * 1000).format('YYYY'), + ) > 2099 + ? t('suspend_user.forever') + : dayjs(user.suspended_until * 1000).format( + t('long_date_with_time', { keyPrefix: 'dates' }), + )} </td> </> )} @@ -357,13 +365,14 @@ const Users: FC = () => { /> <SuspenseUserModal show={suspenseUserModalState.show} + userId={suspenseUserModalState.userId} onClose={() => { handleSuspenseUserModalState({ show: false, userId: '', }); }} - onDelete={(val) => handleDelete(val)} + refreshUsers={refreshUsers} /> </> );