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}
       />
     </>
   );

Reply via email to