This is an automated email from the ASF dual-hosted git repository. robin0716 pushed a commit to branch feat/1.4.0/personal in repository https://gitbox.apache.org/repos/asf/incubator-answer.git
commit 989261eea30c54ebe3247e18f04562a8891b09b2 Author: robin <[email protected]> AuthorDate: Fri Aug 16 11:41:57 2024 +0800 refactor: Update badge description styling in Admin Badges page --- ui/src/common/interface.ts | 15 ++++- ui/src/components/Modal/BadgeModal.tsx | 70 ++++++++++++++++++++++ ui/src/components/Modal/index.tsx | 3 +- ui/src/pages/Admin/Badges/index.tsx | 6 +- ui/src/pages/Layout/index.tsx | 8 ++- .../components/Achievements/index.scss | 5 +- .../components/Achievements/index.tsx | 27 +++++++-- ui/src/services/admin/badges.ts | 8 +-- 8 files changed, 124 insertions(+), 18 deletions(-) diff --git a/ui/src/common/interface.ts b/ui/src/common/interface.ts index 608a2465..a06fec28 100644 --- a/ui/src/common/interface.ts +++ b/ui/src/common/interface.ts @@ -220,11 +220,19 @@ export interface SetNoticeReq { notice_switch: boolean; } +export interface NotificationBadgeAward { + notification_id: string; + badge_id: string; + name: string; + icon: string; + level: number; +} export interface NotificationStatus { inbox: number; achievement: number; revision: number; can_revision: boolean; + badge_award: NotificationBadgeAward | null; } export interface QuestionDetailRes { @@ -758,13 +766,18 @@ export interface BadgeInfo extends BadgeListItem { is_single: boolean; } +export interface AdminBadgeListItem extends BadgeListItem { + group_name: string; + status: string; + description: string; +} + export interface BadgeDetailListReq { page: number; page_size: number; badge_id: string; username?: string | null; } - export interface BadgeDetailListItem { created_at: number; author_user_info: UserInfoBase; diff --git a/ui/src/components/Modal/BadgeModal.tsx b/ui/src/components/Modal/BadgeModal.tsx new file mode 100644 index 00000000..214c1d67 --- /dev/null +++ b/ui/src/components/Modal/BadgeModal.tsx @@ -0,0 +1,70 @@ +import { FC } from 'react'; +import { useTranslation } from 'react-i18next'; +import { useNavigate } from 'react-router-dom'; + +import classNames from 'classnames'; + +import type * as Type from '@/common/interface'; +import { loggedUserInfoStore } from '@/stores'; +import { readNotification, useQueryNotificationStatus } from '@/services'; +import Icon from '../Icon'; + +import Modal from './Modal'; + +interface BadgeModalProps { + badge?: Type.NotificationBadgeAward | null; + visible: boolean; +} +const BadgeModal: FC<BadgeModalProps> = ({ badge, visible }) => { + const { t } = useTranslation('translation', { keyPrefix: 'badges.modal' }); + const { user } = loggedUserInfoStore(); + const navigate = useNavigate(); + const { data } = useQueryNotificationStatus(); + + const handleCancel = async () => { + if (!data) return; + await readNotification(badge?.notification_id); + }; + const handleConfirm = async () => { + await readNotification(badge?.notification_id); + + const url = `/badges/${badge?.badge_id}?username=${user.username}`; + navigate(url); + }; + + return ( + <Modal + title={t('title')} + visible={visible} + onCancel={handleCancel} + onConfirm={handleConfirm} + cancelText={t('close')} + cancelBtnVariant="link" + confirmText={t('confirm')} + confirmBtnVariant="primary" + scrollable={false}> + {badge && ( + <div className="text-center"> + {badge.icon?.startsWith('http') ? ( + <img src={badge.icon} width={96} height={96} alt={badge.name} /> + ) : ( + <Icon + name={badge.icon} + size="96px" + className={classNames( + 'lh-1', + badge.level === 1 && 'bronze', + badge.level === 2 && 'silver', + badge.level === 3 && 'gold', + )} + /> + )} + <h5 className="mt-3">{badge?.name}</h5> + <p>{t('content')}</p> + </div> + )} + </Modal> + ); +}; + +export default BadgeModal; diff --git a/ui/src/components/Modal/index.tsx b/ui/src/components/Modal/index.tsx index 40459196..75d96842 100644 --- a/ui/src/components/Modal/index.tsx +++ b/ui/src/components/Modal/index.tsx @@ -20,6 +20,7 @@ import DefaultModal from './Modal'; import confirm, { Config } from './Confirm'; import LoginToContinueModal from './LoginToContinueModal'; +import BadgeModal from './BadgeModal'; type ModalType = typeof DefaultModal & { confirm: (config: Config) => void; @@ -32,4 +33,4 @@ Modal.confirm = function (props: Config) { export default Modal; -export { LoginToContinueModal }; +export { LoginToContinueModal, BadgeModal }; diff --git a/ui/src/pages/Admin/Badges/index.tsx b/ui/src/pages/Admin/Badges/index.tsx index 448040a4..e76f90eb 100644 --- a/ui/src/pages/Admin/Badges/index.tsx +++ b/ui/src/pages/Admin/Badges/index.tsx @@ -39,7 +39,7 @@ const bgMap = { const PAGE_SIZE = 10; -const Users: FC = () => { +const Badges: FC = () => { const { t } = useTranslation('translation', { keyPrefix: 'admin.badges' }); const [urlSearchParams, setUrlSearchParams] = useSearchParams(); @@ -103,7 +103,7 @@ const Users: FC = () => { </thead> <tbody className="align-middle"> {data?.list.map((badge) => ( - <tr> + <tr key={badge.id}> <td className="d-flex align-items-center"> {badge.icon?.startsWith('http') ? ( <img @@ -157,4 +157,4 @@ const Users: FC = () => { ); }; -export default Users; +export default Badges; diff --git a/ui/src/pages/Layout/index.tsx b/ui/src/pages/Layout/index.tsx index 2029b0fa..e8bab279 100644 --- a/ui/src/pages/Layout/index.tsx +++ b/ui/src/pages/Layout/index.tsx @@ -33,8 +33,9 @@ import { PageTags, HttpErrorContent, } from '@/components'; -import { LoginToContinueModal } from '@/components/Modal'; +import { LoginToContinueModal, BadgeModal } from '@/components/Modal'; import { changeTheme } from '@/utils'; +import { useQueryNotificationStatus } from '@/services'; const Layout: FC = () => { const location = useLocation(); @@ -44,6 +45,7 @@ const Layout: FC = () => { }; const { code: httpStatusCode, reset: httpStatusReset } = errorCodeStore(); const { show: showLoginToContinueModal } = loginToContinueStore(); + const { data: notificationData } = useQueryNotificationStatus(); useEffect(() => { httpStatusReset(); @@ -86,6 +88,10 @@ const Layout: FC = () => { <Footer /> <Customize /> <LoginToContinueModal visible={showLoginToContinueModal} /> + <BadgeModal + badge={notificationData?.badge_award} + visible={Boolean(notificationData?.badge_award)} + /> <ScrollRestoration /> </SWRConfig> </HelmetProvider> diff --git a/ui/src/pages/Users/Notifications/components/Achievements/index.scss b/ui/src/pages/Users/Notifications/components/Achievements/index.scss index fb91193f..9a224921 100644 --- a/ui/src/pages/Users/Notifications/components/Achievements/index.scss +++ b/ui/src/pages/Users/Notifications/components/Achievements/index.scss @@ -18,8 +18,9 @@ */ .achievement-wrap { - .num { + .num, + .icon { width: 60px; flex: none; } -} +} \ No newline at end of file diff --git a/ui/src/pages/Users/Notifications/components/Achievements/index.tsx b/ui/src/pages/Users/Notifications/components/Achievements/index.tsx index 5f2e8f58..c171a427 100644 --- a/ui/src/pages/Users/Notifications/components/Achievements/index.tsx +++ b/ui/src/pages/Users/Notifications/components/Achievements/index.tsx @@ -24,10 +24,13 @@ import classNames from 'classnames'; import isEmpty from 'lodash/isEmpty'; import { Empty } from '@/components'; +import { loggedUserInfoStore } from '@/stores'; import './index.scss'; const Achievements = ({ data, handleReadNotification }) => { + const { user } = loggedUserInfoStore(); + if (!data) { return null; } @@ -50,6 +53,9 @@ const Achievements = ({ data, handleReadNotification }) => { case 'comment': url = `/questions/${question}/${answer}?commentId=${comment}`; break; + case 'badge_award': + url = `/badges/${item.object_info.object_map.badge_id}?username=${user.username}`; + break; default: url = ''; } @@ -60,13 +66,22 @@ const Achievements = ({ data, handleReadNotification }) => { 'd-flex border-start-0 border-end-0 py-3', !item.is_read && 'warning', )}> - {item.rank > 0 && ( - <div className="text-success num text-end">{`+${item.rank}`}</div> - )} - {item.rank === 0 && <div className="num text-end">{item.rank}</div>} - {item.rank < 0 && ( - <div className="text-danger num text-end">{`${item.rank}`}</div> + {item.object_info.object_type === 'badge_award' ? ( + <div className="icon text-end">👏</div> + ) : ( + <> + {item.rank > 0 && ( + <div className="text-success num text-end">{`+${item.rank}`}</div> + )} + {item.rank === 0 && ( + <div className="num text-end">{item.rank}</div> + )} + {item.rank < 0 && ( + <div className="text-danger num text-end">{`${item.rank}`}</div> + )} + </> )} + <div className="d-flex flex-column ms-3 flex-fill"> <Link to={url} onClick={() => handleReadNotification(item.id)}> {item.object_info.title} diff --git a/ui/src/services/admin/badges.ts b/ui/src/services/admin/badges.ts index b984164e..64685bc5 100644 --- a/ui/src/services/admin/badges.ts +++ b/ui/src/services/admin/badges.ts @@ -25,10 +25,10 @@ import type * as Type from '@/common/interface'; export const useQueryBadges = (params) => { const apiUrl = `/answer/admin/api/badges?${qs.stringify(params)}`; - const { data, error, mutate } = useSWR<Type.ListResult, Error>( - apiUrl, - request.instance.get, - ); + const { data, error, mutate } = useSWR< + Type.ListResult<Type.AdminBadgeListItem>, + Error + >(apiUrl, request.instance.get); return { data, isLoading: !data && !error,
