Skip to content

Commit

Permalink
feat: update level
Browse files Browse the repository at this point in the history
  • Loading branch information
521xueweihan committed Nov 8, 2024
1 parent ab8cc9a commit 83f1985
Show file tree
Hide file tree
Showing 15 changed files with 242 additions and 112 deletions.
2 changes: 1 addition & 1 deletion data/level.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ HelloGitHub 社区等级是一套用于统计开源爱好者,为 HelloGitHub

HelloGitHub 社区等级是根据贡献值实时计算和升级。

![](https://img.hellogithub.com/article/level.png)
![](https://img.hellogithub.com/article/lcwa8rGSYoX0JAW_1731061719.png)

## 获得和扣除贡献值

Expand Down
2 changes: 1 addition & 1 deletion data/level_en.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Everyone is encouraged to share interesting, beginner-friendly open-source proje

The HelloGitHub Community Level is calculated and upgraded in real-time based on contribution points.

![](https://img.hellogithub.com/article/lSthE0PgBGoUCQd_1723623130.png)
![](https://img.hellogithub.com/article/9fR85MsourUyweN_1731061742.png)

## Earning and Deducting Contribution Points

Expand Down
3 changes: 3 additions & 0 deletions public/locales/en/profile.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
"comment_hot": "Hot comment on",
"comment_hot2": "project",
"comment_bad": "Posted meaningless/spam comment, deducted 2 contribution points.",
"reply_bad": "Posting meaningless/spam replies, deduct 2 contribution points.",
"contribute_code": "Contribute code to the community, gain {{value}} contribution points.",
"feedback_bug": "Report valuable issues to the community and earn {{value}} contribution points.",
"submit_repo": "Shared an excellent open source project",
"default": "Enriched community content",
"empty": "No activity"
Expand Down
2 changes: 1 addition & 1 deletion public/locales/en/rank.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
"contribution": {
"title": "Contribution Ranking",
"nav": "{{month}} {{year}} Contribution Ranking",
"p_text": "<strong>HelloGitHub Contribution Ranking</strong> is a ranking based on the contribution value of users in the HelloGitHub open source community, updated with the current month's data on the 15th of each month. Users can earn contribution points in the following ways: sharing an open source project earns 5 points, posting a comment earns 2 points, and if a comment is selected as a hot comment, an additional 10 contribution points will be awarded.",
"p_text": "<strong>HelloGitHub Contribution Ranking</strong> is a ranking based on the contribution points users earn within the HelloGitHub open source community, reflecting the contributions and prestige of open source enthusiasts in this community. Users can earn contribution points in the following ways: sharing open source projects grants 5 points, posting comments earns 2 points, and if a comment is selected as a hot comment, an additional 10 contribution points can be earned.",
"thead": {
"position": "Rank",
"name": "User",
Expand Down
3 changes: 3 additions & 0 deletions public/locales/zh/profile.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@
"comment_hot": "对开源项目",
"comment_hot2": "的评论被选为热评",
"comment_bad": "发布无意义/灌水评论,扣除 2 点贡献值。",
"reply_bad": "发布无意义/灌水回复,扣除 2 点贡献值。",
"contribute_code": "为社区贡献代码,获得 {{value}} 点贡献值。",
"feedback_bug": "向社区反馈有价值的问题,获得 {{value}} 点贡献值。",
"submit_repo": "分享优秀的开源项目",
"default": "丰富社区内容资源",
"empty": "暂无动态"
Expand Down
2 changes: 1 addition & 1 deletion public/locales/zh/rank.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
"contribution": {
"title": "用户贡献排名",
"nav": "{{year}} 年 {{month}} 月用户贡献排行榜",
"p_text": "<strong>「HelloGitHub 贡献排名」</strong>是根据用户在 HelloGitHub 开源社区中的贡献值进行排名,每月 15 日更新当月的数据。用户可通过以下方式获得贡献值:分享开源项目可获得 5 点,发表评论获得 2 点,若评论被选为热评,则可额外获得 10 点贡献值。",
"p_text": "<strong>「HelloGitHub 贡献排名」</strong>是根据用户在 HelloGitHub 开源社区中的贡献值进行排名,用来体现开源爱好者在本社区的贡献和声望。用户可通过以下方式获得贡献值:分享开源项目可获得 5 点,发表评论获得 2 点,若评论被选为热评,则可额外获得 10 点贡献值。",
"thead": {
"position": "排名",
"name": "用户",
Expand Down
13 changes: 12 additions & 1 deletion src/components/report/Report.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { AiFillCaretDown, AiFillCaretUp } from 'react-icons/ai';
import { IoMdRemove, IoMdTrendingDown, IoMdTrendingUp } from 'react-icons/io';

import { LevelRender } from '@/components/user/Common';

import { NoPrefetchLink } from '../links/CustomLink';

import { RankDataItem } from '@/types/rank';
Expand Down Expand Up @@ -59,10 +61,11 @@ export const TrendColumnRender = (

export const ContributionColumnRender = (
row: RankDataItem,
_showPercent: boolean,
i18n_lang: string
) => {
let text = '-';
if (row.change !== null) {
if (row.change !== 0) {
text = `+${row.change}`;
} else {
return <span>{text}</span>;
Expand Down Expand Up @@ -108,3 +111,11 @@ export const PositionColumnRender = (row: RankDataItem) => {
</div>
);
};

export const LevelColumnRender = (
row: RankDataItem,
_showPercent: boolean,
i18n_lang: string
) => {
return LevelRender(row.rating as number, true, i18n_lang);
};
2 changes: 1 addition & 1 deletion src/components/side/Side.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export const Side = ({ isHome }: { isHome: boolean }) => {
<div className='relative mt-2 ml-3 max-w-[244px]'>
<div className='space-y-2'>
<div className='rounded-lg bg-white pl-3 pr-3 pt-3 pb-2.5 dark:bg-gray-800'>
<UserStatus t={t} />
<UserStatus t={t} i18n_lang={i18n.language} />
</div>
{!isValidating && (
<SideAd data={adverts} t={t} i18n_lang={i18n.language} />
Expand Down
225 changes: 131 additions & 94 deletions src/components/side/UserStatus.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,123 @@
import { useRouter } from 'next/router';
import { useMemo } from 'react';
import { AiOutlineBell, AiOutlineQuestionCircle } from 'react-icons/ai';

import { useLoginContext } from '@/hooks/useLoginContext';

import { NoPrefetchLink } from '@/components/links/CustomLink';
import { LevelRender } from '@/components/user/Common';

import { DEFAULT_AVATAR } from '@/utils/constants';

import SideLoginButton from './SideLoginButton';

import { SideProps } from '@/types/home';

export default function UserStatus({ t }: SideProps) {
const UserAvatar = ({ uid, avatar }: { uid: string; avatar: string }) => (
<NoPrefetchLink href={`/user/${uid}`}>
<a>
<div className='bg-img top-0 left-0 h-10 w-10 shrink-0 grow-0 cursor-pointer rounded-lg object-cover'>
<img
className='rounded'
width='40'
height='40'
src={avatar || DEFAULT_AVATAR}
alt='side_avatar'
/>
</div>
</a>
</NoPrefetchLink>
);

const NotificationBell = ({ unreadCount }: { unreadCount: number }) => (
<span className='relative inline-block'>
<AiOutlineBell
size={20}
className='text-gray-500 hover:text-blue-500 dark:text-gray-400 dark:hover:text-blue-500'
/>
{unreadCount > 0 && (
<span className='absolute top-0.5 right-0 flex h-1.5 w-1.5 translate-x-1/2 -translate-y-1/2'>
<span className='absolute inline-flex h-full w-full animate-ping rounded-full bg-blue-400 opacity-75' />
<span className='relative inline-flex h-1.5 w-1.5 rounded-full bg-blue-500' />
</span>
)}
</span>
);

const LevelProgress = ({
contribute,
nextLevelScore,
t,
}: {
contribute: number;
nextLevelScore: number | null;
t: (key: string) => string;
}) => (
<div className='mt-1'>
<div className='flex justify-between text-sm'>
<div className='cursor-pointer text-gray-400'>
<NoPrefetchLink href='/help/level'>
<a className='align-[-5px] text-xs'>
<span className='mr-0.5'>{t('user_side.contribute_label')}</span>
<AiOutlineQuestionCircle className='inline-block align-[-2px]' />
</a>
</NoPrefetchLink>
</div>
<span className='text-xl text-blue-500'>
{contribute}
<span className='mx-0.5'>/</span>
{nextLevelScore || 'Max'}
</span>
</div>
<div className='flex h-1.5 w-full overflow-hidden rounded-full bg-gray-200 dark:bg-gray-700'>
<div
className='flex flex-col justify-center overflow-hidden bg-blue-500'
style={{
width: `${
!nextLevelScore ? 100 : (contribute / nextLevelScore) * 100
}%`,
}}
/>
</div>
</div>
);

const UserFooter = ({
uid,
isAdmin,
logout,
t,
}: {
uid: string;
isAdmin: boolean;
logout: () => void;
t: (key: string) => string;
}) => (
<div className='mt-3 flex justify-between border-t text-xs dark:border-gray-700'>
<NoPrefetchLink href={`/user/${uid}`}>
<a className='pl-1 pt-2 pb-1 text-gray-400 hover:text-blue-500 hover:underline'>
{t('user_side.profile')}
</a>
</NoPrefetchLink>
{isAdmin ? (
<a href='/taichi/'>
<div className='pr-1 pt-2 pb-1 text-gray-400 hover:text-blue-500 hover:underline'>
{t('user_side.admin')}
</div>
</a>
) : (
<div
className='cursor-pointer pr-1 pt-2 pb-1 text-gray-400 hover:text-blue-500 hover:underline'
onClick={logout}
>
{t('user_side.logout')}
</div>
)}
</div>
);

export default function UserStatus({ t, i18n_lang }: SideProps) {
const router = useRouter();
const { userInfo, isLogin, logout } = useLoginContext();
const levelPercent = useMemo(() => {
if (
typeof userInfo?.contribute === 'number' &&
typeof userInfo?.next_level_score === 'number'
) {
return (userInfo.contribute / userInfo?.next_level_score) * 100;
}
// next_level_score 为 null 时则达到了最大等级
return !userInfo?.next_level_score ? 100 : 0;
}, [userInfo]);

if (!isLogin || !userInfo?.success) {
return <SideLoginButton text={t('user_side.login')} />;
Expand All @@ -33,99 +126,43 @@ export default function UserStatus({ t }: SideProps) {
return (
<div className='relative'>
<div className='flex'>
<NoPrefetchLink href={`/user/${userInfo.uid}`}>
<a>
<div className='bg-img top-0 left-0 h-10 w-10 shrink-0 grow-0 cursor-pointer rounded-lg object-cover'>
<img
className='rounded'
width='40'
height='40'
src={userInfo?.avatar || DEFAULT_AVATAR}
alt='side_avatar'
/>
</div>
</a>
</NoPrefetchLink>
<UserAvatar uid={userInfo.uid} avatar={userInfo.avatar} />
<div className='ml-2 w-full'>
<div className='relative flex h-5 items-center'>
<div className='block w-14 truncate align-baseline font-medium dark:text-gray-300 lg:w-24'>
{userInfo.nickname}
</div>
<div className='flex-grow' />
<div className='justify-end'>
<div
className='flex cursor-pointer flex-row'
onClick={() => {
router.push('/notification');
}}
>
<span className='relative inline-block'>
<AiOutlineBell
size={20}
className='text-gray-500 hover:text-blue-500 dark:text-gray-400 dark:hover:text-blue-500'
/>
{userInfo?.unread.total > 0 && (
<span className='absolute top-0.5 right-0 flex h-1.5 w-1.5 translate-x-1/2 -translate-y-1/2'>
<span className='absolute inline-flex h-full w-full animate-ping rounded-full bg-blue-400 opacity-75' />
<span className='relative inline-flex h-1.5 w-1.5 rounded-full bg-blue-500' />
</span>
)}
</span>
</div>
<div
className='cursor-pointer justify-end'
onClick={() => router.push('/notification')}
>
<NotificationBell unreadCount={userInfo?.unread.total || 0} />
</div>
</div>
<div className='text-sm font-bold text-blue-500'>
Lv.{userInfo.level}
</div>
</div>
</div>
{/* 等级展示 */}
<div className='mt-1'>
<div className='flex justify-between text-sm'>
<div className='cursor-pointer text-gray-400'>
<NoPrefetchLink href='/help/level'>
<a className='align-[-5px] text-xs'>
<span className='mr-0.5'>
{t('user_side.contribute_label')}
</span>
<AiOutlineQuestionCircle className='inline-block align-[-2px]' />
</a>
</NoPrefetchLink>
</div>
<span className='text-xl text-blue-500'>
{userInfo.contribute}
<span className='mx-0.5'>/</span>
{userInfo.next_level_score || 'Max'}
</span>
</div>
<div className='flex h-1.5 w-full overflow-hidden rounded-full bg-gray-200 dark:bg-gray-700'>
<div
className='flex flex-col justify-center overflow-hidden bg-blue-500'
style={{ width: `${levelPercent}%` }}
/>
</div>
</div>
<div className='mt-3 flex justify-between border-t text-xs dark:border-gray-700'>
<NoPrefetchLink href={`/user/${userInfo.uid}`}>
<a className='pl-1 pt-2 pb-1 text-gray-400 hover:text-blue-500 hover:underline'>
{t('user_side.profile')}
</a>
</NoPrefetchLink>
{userInfo.permission?.code == 'super' ? (
<a href='/taichi/'>
<div className='pr-1 pt-2 pb-1 text-gray-400 hover:text-blue-500 hover:underline'>
{t('user_side.admin')}
<div className='flex items-center justify-between'>
<div className='text-sm font-bold text-blue-500'>
Lv.{userInfo.level}
</div>
<div className='text-xs'>
{LevelRender(userInfo.level, false, i18n_lang)}
</div>
</a>
) : (
<div
className='cursor-pointer pr-1 pt-2 pb-1 text-gray-400 hover:text-blue-500 hover:underline'
onClick={logout}
>
{t('user_side.logout')}
</div>
)}
</div>
</div>

<LevelProgress
contribute={userInfo.contribute}
nextLevelScore={userInfo.next_level_score}
t={t}
/>

<UserFooter
uid={userInfo.uid}
isAdmin={userInfo.permission?.code === 'super'}
logout={logout}
t={t}
/>
</div>
);
}
Loading

0 comments on commit 83f1985

Please sign in to comment.