Skip to content

Commit 83f1985

Browse files
committed
feat: update level
1 parent ab8cc9a commit 83f1985

File tree

15 files changed

+242
-112
lines changed

15 files changed

+242
-112
lines changed

data/level.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ HelloGitHub 社区等级是一套用于统计开源爱好者,为 HelloGitHub
88

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

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

1313
## 获得和扣除贡献值
1414

data/level_en.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ Everyone is encouraged to share interesting, beginner-friendly open-source proje
88

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

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

1313
## Earning and Deducting Contribution Points
1414

public/locales/en/profile.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424
"comment_hot": "Hot comment on",
2525
"comment_hot2": "project",
2626
"comment_bad": "Posted meaningless/spam comment, deducted 2 contribution points.",
27+
"reply_bad": "Posting meaningless/spam replies, deduct 2 contribution points.",
28+
"contribute_code": "Contribute code to the community, gain {{value}} contribution points.",
29+
"feedback_bug": "Report valuable issues to the community and earn {{value}} contribution points.",
2730
"submit_repo": "Shared an excellent open source project",
2831
"default": "Enriched community content",
2932
"empty": "No activity"

public/locales/en/rank.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
"contribution": {
4949
"title": "Contribution Ranking",
5050
"nav": "{{month}} {{year}} Contribution Ranking",
51-
"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.",
51+
"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.",
5252
"thead": {
5353
"position": "Rank",
5454
"name": "User",

public/locales/zh/profile.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424
"comment_hot": "对开源项目",
2525
"comment_hot2": "的评论被选为热评",
2626
"comment_bad": "发布无意义/灌水评论,扣除 2 点贡献值。",
27+
"reply_bad": "发布无意义/灌水回复,扣除 2 点贡献值。",
28+
"contribute_code": "为社区贡献代码,获得 {{value}} 点贡献值。",
29+
"feedback_bug": "向社区反馈有价值的问题,获得 {{value}} 点贡献值。",
2730
"submit_repo": "分享优秀的开源项目",
2831
"default": "丰富社区内容资源",
2932
"empty": "暂无动态"

public/locales/zh/rank.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
"contribution": {
4949
"title": "用户贡献排名",
5050
"nav": "{{year}} 年 {{month}} 月用户贡献排行榜",
51-
"p_text": "<strong>「HelloGitHub 贡献排名」</strong>是根据用户在 HelloGitHub 开源社区中的贡献值进行排名,每月 15 日更新当月的数据。用户可通过以下方式获得贡献值:分享开源项目可获得 5 点,发表评论获得 2 点,若评论被选为热评,则可额外获得 10 点贡献值。",
51+
"p_text": "<strong>「HelloGitHub 贡献排名」</strong>是根据用户在 HelloGitHub 开源社区中的贡献值进行排名,用来体现开源爱好者在本社区的贡献和声望。用户可通过以下方式获得贡献值:分享开源项目可获得 5 点,发表评论获得 2 点,若评论被选为热评,则可额外获得 10 点贡献值。",
5252
"thead": {
5353
"position": "排名",
5454
"name": "用户",

src/components/report/Report.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { AiFillCaretDown, AiFillCaretUp } from 'react-icons/ai';
22
import { IoMdRemove, IoMdTrendingDown, IoMdTrendingUp } from 'react-icons/io';
33

4+
import { LevelRender } from '@/components/user/Common';
5+
46
import { NoPrefetchLink } from '../links/CustomLink';
57

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

6062
export const ContributionColumnRender = (
6163
row: RankDataItem,
64+
_showPercent: boolean,
6265
i18n_lang: string
6366
) => {
6467
let text = '-';
65-
if (row.change !== null) {
68+
if (row.change !== 0) {
6669
text = `+${row.change}`;
6770
} else {
6871
return <span>{text}</span>;
@@ -108,3 +111,11 @@ export const PositionColumnRender = (row: RankDataItem) => {
108111
</div>
109112
);
110113
};
114+
115+
export const LevelColumnRender = (
116+
row: RankDataItem,
117+
_showPercent: boolean,
118+
i18n_lang: string
119+
) => {
120+
return LevelRender(row.rating as number, true, i18n_lang);
121+
};

src/components/side/Side.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ export const Side = ({ isHome }: { isHome: boolean }) => {
4444
<div className='relative mt-2 ml-3 max-w-[244px]'>
4545
<div className='space-y-2'>
4646
<div className='rounded-lg bg-white pl-3 pr-3 pt-3 pb-2.5 dark:bg-gray-800'>
47-
<UserStatus t={t} />
47+
<UserStatus t={t} i18n_lang={i18n.language} />
4848
</div>
4949
{!isValidating && (
5050
<SideAd data={adverts} t={t} i18n_lang={i18n.language} />

src/components/side/UserStatus.tsx

Lines changed: 131 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,123 @@
11
import { useRouter } from 'next/router';
2-
import { useMemo } from 'react';
32
import { AiOutlineBell, AiOutlineQuestionCircle } from 'react-icons/ai';
43

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

76
import { NoPrefetchLink } from '@/components/links/CustomLink';
7+
import { LevelRender } from '@/components/user/Common';
88

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

1111
import SideLoginButton from './SideLoginButton';
1212

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

15-
export default function UserStatus({ t }: SideProps) {
15+
const UserAvatar = ({ uid, avatar }: { uid: string; avatar: string }) => (
16+
<NoPrefetchLink href={`/user/${uid}`}>
17+
<a>
18+
<div className='bg-img top-0 left-0 h-10 w-10 shrink-0 grow-0 cursor-pointer rounded-lg object-cover'>
19+
<img
20+
className='rounded'
21+
width='40'
22+
height='40'
23+
src={avatar || DEFAULT_AVATAR}
24+
alt='side_avatar'
25+
/>
26+
</div>
27+
</a>
28+
</NoPrefetchLink>
29+
);
30+
31+
const NotificationBell = ({ unreadCount }: { unreadCount: number }) => (
32+
<span className='relative inline-block'>
33+
<AiOutlineBell
34+
size={20}
35+
className='text-gray-500 hover:text-blue-500 dark:text-gray-400 dark:hover:text-blue-500'
36+
/>
37+
{unreadCount > 0 && (
38+
<span className='absolute top-0.5 right-0 flex h-1.5 w-1.5 translate-x-1/2 -translate-y-1/2'>
39+
<span className='absolute inline-flex h-full w-full animate-ping rounded-full bg-blue-400 opacity-75' />
40+
<span className='relative inline-flex h-1.5 w-1.5 rounded-full bg-blue-500' />
41+
</span>
42+
)}
43+
</span>
44+
);
45+
46+
const LevelProgress = ({
47+
contribute,
48+
nextLevelScore,
49+
t,
50+
}: {
51+
contribute: number;
52+
nextLevelScore: number | null;
53+
t: (key: string) => string;
54+
}) => (
55+
<div className='mt-1'>
56+
<div className='flex justify-between text-sm'>
57+
<div className='cursor-pointer text-gray-400'>
58+
<NoPrefetchLink href='/help/level'>
59+
<a className='align-[-5px] text-xs'>
60+
<span className='mr-0.5'>{t('user_side.contribute_label')}</span>
61+
<AiOutlineQuestionCircle className='inline-block align-[-2px]' />
62+
</a>
63+
</NoPrefetchLink>
64+
</div>
65+
<span className='text-xl text-blue-500'>
66+
{contribute}
67+
<span className='mx-0.5'>/</span>
68+
{nextLevelScore || 'Max'}
69+
</span>
70+
</div>
71+
<div className='flex h-1.5 w-full overflow-hidden rounded-full bg-gray-200 dark:bg-gray-700'>
72+
<div
73+
className='flex flex-col justify-center overflow-hidden bg-blue-500'
74+
style={{
75+
width: `${
76+
!nextLevelScore ? 100 : (contribute / nextLevelScore) * 100
77+
}%`,
78+
}}
79+
/>
80+
</div>
81+
</div>
82+
);
83+
84+
const UserFooter = ({
85+
uid,
86+
isAdmin,
87+
logout,
88+
t,
89+
}: {
90+
uid: string;
91+
isAdmin: boolean;
92+
logout: () => void;
93+
t: (key: string) => string;
94+
}) => (
95+
<div className='mt-3 flex justify-between border-t text-xs dark:border-gray-700'>
96+
<NoPrefetchLink href={`/user/${uid}`}>
97+
<a className='pl-1 pt-2 pb-1 text-gray-400 hover:text-blue-500 hover:underline'>
98+
{t('user_side.profile')}
99+
</a>
100+
</NoPrefetchLink>
101+
{isAdmin ? (
102+
<a href='/taichi/'>
103+
<div className='pr-1 pt-2 pb-1 text-gray-400 hover:text-blue-500 hover:underline'>
104+
{t('user_side.admin')}
105+
</div>
106+
</a>
107+
) : (
108+
<div
109+
className='cursor-pointer pr-1 pt-2 pb-1 text-gray-400 hover:text-blue-500 hover:underline'
110+
onClick={logout}
111+
>
112+
{t('user_side.logout')}
113+
</div>
114+
)}
115+
</div>
116+
);
117+
118+
export default function UserStatus({ t, i18n_lang }: SideProps) {
16119
const router = useRouter();
17120
const { userInfo, isLogin, logout } = useLoginContext();
18-
const levelPercent = useMemo(() => {
19-
if (
20-
typeof userInfo?.contribute === 'number' &&
21-
typeof userInfo?.next_level_score === 'number'
22-
) {
23-
return (userInfo.contribute / userInfo?.next_level_score) * 100;
24-
}
25-
// next_level_score 为 null 时则达到了最大等级
26-
return !userInfo?.next_level_score ? 100 : 0;
27-
}, [userInfo]);
28121

29122
if (!isLogin || !userInfo?.success) {
30123
return <SideLoginButton text={t('user_side.login')} />;
@@ -33,99 +126,43 @@ export default function UserStatus({ t }: SideProps) {
33126
return (
34127
<div className='relative'>
35128
<div className='flex'>
36-
<NoPrefetchLink href={`/user/${userInfo.uid}`}>
37-
<a>
38-
<div className='bg-img top-0 left-0 h-10 w-10 shrink-0 grow-0 cursor-pointer rounded-lg object-cover'>
39-
<img
40-
className='rounded'
41-
width='40'
42-
height='40'
43-
src={userInfo?.avatar || DEFAULT_AVATAR}
44-
alt='side_avatar'
45-
/>
46-
</div>
47-
</a>
48-
</NoPrefetchLink>
129+
<UserAvatar uid={userInfo.uid} avatar={userInfo.avatar} />
49130
<div className='ml-2 w-full'>
50131
<div className='relative flex h-5 items-center'>
51132
<div className='block w-14 truncate align-baseline font-medium dark:text-gray-300 lg:w-24'>
52133
{userInfo.nickname}
53134
</div>
54135
<div className='flex-grow' />
55-
<div className='justify-end'>
56-
<div
57-
className='flex cursor-pointer flex-row'
58-
onClick={() => {
59-
router.push('/notification');
60-
}}
61-
>
62-
<span className='relative inline-block'>
63-
<AiOutlineBell
64-
size={20}
65-
className='text-gray-500 hover:text-blue-500 dark:text-gray-400 dark:hover:text-blue-500'
66-
/>
67-
{userInfo?.unread.total > 0 && (
68-
<span className='absolute top-0.5 right-0 flex h-1.5 w-1.5 translate-x-1/2 -translate-y-1/2'>
69-
<span className='absolute inline-flex h-full w-full animate-ping rounded-full bg-blue-400 opacity-75' />
70-
<span className='relative inline-flex h-1.5 w-1.5 rounded-full bg-blue-500' />
71-
</span>
72-
)}
73-
</span>
74-
</div>
136+
<div
137+
className='cursor-pointer justify-end'
138+
onClick={() => router.push('/notification')}
139+
>
140+
<NotificationBell unreadCount={userInfo?.unread.total || 0} />
75141
</div>
76142
</div>
77-
<div className='text-sm font-bold text-blue-500'>
78-
Lv.{userInfo.level}
79-
</div>
80-
</div>
81-
</div>
82-
{/* 等级展示 */}
83-
<div className='mt-1'>
84-
<div className='flex justify-between text-sm'>
85-
<div className='cursor-pointer text-gray-400'>
86-
<NoPrefetchLink href='/help/level'>
87-
<a className='align-[-5px] text-xs'>
88-
<span className='mr-0.5'>
89-
{t('user_side.contribute_label')}
90-
</span>
91-
<AiOutlineQuestionCircle className='inline-block align-[-2px]' />
92-
</a>
93-
</NoPrefetchLink>
94-
</div>
95-
<span className='text-xl text-blue-500'>
96-
{userInfo.contribute}
97-
<span className='mx-0.5'>/</span>
98-
{userInfo.next_level_score || 'Max'}
99-
</span>
100-
</div>
101-
<div className='flex h-1.5 w-full overflow-hidden rounded-full bg-gray-200 dark:bg-gray-700'>
102-
<div
103-
className='flex flex-col justify-center overflow-hidden bg-blue-500'
104-
style={{ width: `${levelPercent}%` }}
105-
/>
106-
</div>
107-
</div>
108-
<div className='mt-3 flex justify-between border-t text-xs dark:border-gray-700'>
109-
<NoPrefetchLink href={`/user/${userInfo.uid}`}>
110-
<a className='pl-1 pt-2 pb-1 text-gray-400 hover:text-blue-500 hover:underline'>
111-
{t('user_side.profile')}
112-
</a>
113-
</NoPrefetchLink>
114-
{userInfo.permission?.code == 'super' ? (
115-
<a href='/taichi/'>
116-
<div className='pr-1 pt-2 pb-1 text-gray-400 hover:text-blue-500 hover:underline'>
117-
{t('user_side.admin')}
143+
<div className='flex items-center justify-between'>
144+
<div className='text-sm font-bold text-blue-500'>
145+
Lv.{userInfo.level}
146+
</div>
147+
<div className='text-xs'>
148+
{LevelRender(userInfo.level, false, i18n_lang)}
118149
</div>
119-
</a>
120-
) : (
121-
<div
122-
className='cursor-pointer pr-1 pt-2 pb-1 text-gray-400 hover:text-blue-500 hover:underline'
123-
onClick={logout}
124-
>
125-
{t('user_side.logout')}
126150
</div>
127-
)}
151+
</div>
128152
</div>
153+
154+
<LevelProgress
155+
contribute={userInfo.contribute}
156+
nextLevelScore={userInfo.next_level_score}
157+
t={t}
158+
/>
159+
160+
<UserFooter
161+
uid={userInfo.uid}
162+
isAdmin={userInfo.permission?.code === 'super'}
163+
logout={logout}
164+
t={t}
165+
/>
129166
</div>
130167
);
131168
}

0 commit comments

Comments
 (0)