|
@@ -195,38 +212,8 @@ function TableRow({
>
{user.username}
- {user.isSponsor && (
-
-
- {t('sponsor')}
-
- )}
-
- {isCurrentUser && (
-
+{user.achievements && user.achievements.length > 0 && (
+
)}
@@ -295,4 +282,4 @@ function RankBadge({ rank }: { rank: number }) {
{rank}
);
-}
+}
\ No newline at end of file
diff --git a/frontend/components/leaderboard/types.ts b/frontend/components/leaderboard/types.ts
index e83a0fd4..54b2e897 100644
--- a/frontend/components/leaderboard/types.ts
+++ b/frontend/components/leaderboard/types.ts
@@ -1,3 +1,5 @@
+import type { Achievement } from '@/lib/achievements';
+
export interface User {
id: number;
userId: string;
@@ -7,9 +9,10 @@ export interface User {
avatar: string;
change: number;
isSponsor?: boolean;
+ achievements?: Achievement[];
}
export interface CurrentUser {
id: string;
username: string;
-}
+}
\ No newline at end of file
diff --git a/frontend/lib/github-stars.ts b/frontend/lib/github-stars.ts
index 1d799c31..b517aa2f 100644
--- a/frontend/lib/github-stars.ts
+++ b/frontend/lib/github-stars.ts
@@ -38,11 +38,49 @@ export async function resolveGitHubLogin(providerId: string): Promise {
+ const token = getToken();
+ if (!token) return [];
+
+ const all: StargazerEntry[] = [];
+
+ for (let page = 1; page <= MAX_PAGES; page++) {
+ const url = `https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/stargazers?per_page=100&page=${page}`;
+ try {
+ const res = await fetch(url, {
+ headers: makeHeaders(),
+ next: { revalidate: 3600 },
+ });
+ if (!res.ok) {
+ console.warn(`⚠️ GitHub stargazers API error [${url}]: ${res.status} ${res.statusText}`);
+ break;
+ }
+
+ const pageData: { login: string; avatar_url: string }[] = await res.json();
+ if (pageData.length === 0) break;
+
+ for (const s of pageData) {
+ all.push({
+ login: s.login.toLowerCase(),
+ avatarBase: s.avatar_url.split('?')[0],
+ });
+ }
+
+ if (pageData.length < 100) break;
+ } catch (err) {
+ console.error(`❌ Failed to fetch GitHub stargazers [${url}]:`, err);
+ break;
+ }
+ }
+
+ return all;
+}
+
export async function checkHasStarredRepo(
githubLogin: string,
): Promise {
@@ -57,7 +95,7 @@ export async function checkHasStarredRepo(
try {
const res = await fetch(url, {
headers: makeHeaders(),
- next: { revalidate: 3600 }, // cache 1 hour per page
+ next: { revalidate: 3600 },
});
if (!res.ok) {
@@ -67,14 +105,11 @@ export async function checkHasStarredRepo(
const stargazers: { login: string }[] = await res.json();
- // Empty page = we've exhausted all stargazers
if (stargazers.length === 0) return false;
if (stargazers.some((s) => s.login.toLowerCase() === loginLower)) {
return true;
}
-
- // Last page (less than 100 results) — user not found
if (stargazers.length < 100) return false;
} catch (err) {
console.error('❌ Failed to check GitHub stargazers:', err);
@@ -83,4 +118,4 @@ export async function checkHasStarredRepo(
}
return false;
-}
+}
\ No newline at end of file
diff --git a/frontend/messages/en.json b/frontend/messages/en.json
index f7a35303..2f1cc9bf 100644
--- a/frontend/messages/en.json
+++ b/frontend/messages/en.json
@@ -1202,7 +1202,9 @@
"attachFile": "Attach File",
"success": "Thank you! Your feedback has been sent.",
"error": "Something went wrong. Please try again.",
- "requiredField": "Please fill out this field."
+ "requiredField": "Please fill out this field.",
+ "tooManyFiles": "You can attach up to {max} files.",
+ "removeFile": "Remove {name}"
},
"achievements": {
"title": "Achievements",
@@ -1211,7 +1213,9 @@
"expand": "View all",
"collapse": "Collapse",
"clickInfo": "Click for info",
- "clickBack": "Click to flip back"
+ "clickBack": "Click to flip back",
+ "more": "more",
+ "moreCount": "{count} more"
},
"badges": {
"first_blood": {
diff --git a/frontend/messages/pl.json b/frontend/messages/pl.json
index 6d72c0a9..5ca8de2f 100644
--- a/frontend/messages/pl.json
+++ b/frontend/messages/pl.json
@@ -1088,6 +1088,7 @@
"profile": {
"title": "Profil",
"subtitle": "Zarządzaj swoim profilem publicznym i preferencjami",
+ "sponsor": "Sponsor",
"becomeSponsor": "Zostań sponsorem",
"supportAgain": "Okaż więcej wsparcia",
"points": "Pkt",
@@ -1203,7 +1204,9 @@
"attachFile": "Dołącz plik",
"success": "Dziękujemy! Twoja opinia została wysłana.",
"error": "Coś poszło nie tak. Spróbuj ponownie.",
- "requiredField": "Proszę wypełnić to pole."
+ "requiredField": "Proszę wypełnić to pole.",
+ "tooManyFiles": "Możesz dołączyć maksymalnie {max} pliki.",
+ "removeFile": "Usuń {name}"
},
"achievements": {
"title": "Osiągnięcia",
@@ -1212,7 +1215,9 @@
"expand": "Zobacz wszystkie",
"collapse": "Zwiń",
"clickInfo": "Kliknij po info",
- "clickBack": "Kliknij, aby wrócić"
+ "clickBack": "Kliknij, aby wrócić",
+ "more": "więcej",
+ "moreCount": "{count} więcej"
},
"badges": {
"first_blood": {
diff --git a/frontend/messages/uk.json b/frontend/messages/uk.json
index 01de8ea0..7c144be5 100644
--- a/frontend/messages/uk.json
+++ b/frontend/messages/uk.json
@@ -1120,7 +1120,7 @@
"avgScore": "Середній бал",
"mastered": "Засвоєно",
"review": "Потребує повторення",
- "study": "Вивчення",
+ "study": "Потребує вивчення",
"totalAttempts": "Всього спроб",
"noActivity": "Ви ще не проходили жодного квізу. Почніть навчання та відстежуйте свій прогрес тут!",
"startQuiz": "Почати перший квіз",
@@ -1206,7 +1206,9 @@
"attachFile": "Прикріпити файл",
"success": "Дякуємо! Ваш відгук надіслано.",
"error": "Щось пішло не так. Спробуйте ще раз.",
- "requiredField": "Будь ласка, заповніть це поле."
+ "requiredField": "Будь ласка, заповніть це поле.",
+ "tooManyFiles": "Можна прикріпити не більше {max} файлів.",
+ "removeFile": "Видалити {name}"
},
"achievements": {
"title": "Досягнення",
@@ -1249,7 +1251,7 @@
"hint": "Отримайте 100% у 3 квізах"
},
"supporter": {
- "name": "Підтримувач",
+ "name": "Спонсор",
"desc": "Дякуємо за підтримку!",
"hint": "Станьте спонсором на GitHub"
},
@@ -1279,7 +1281,7 @@
"hint": "Наберіть 1000 балів"
},
"star_gazer": {
- "name": "Зоряний глядач",
+ "name": "Володар зірки",
"desc": "Ти додав зірочку репозиторію DevLovers на GitHub!",
"hint": "Постав зірочку нашому GitHub репозиторію"
},
@@ -1304,7 +1306,7 @@
"hint": "Наберіть 100 балів"
},
"deep_diver": {
- "name": "Глибокий ныряльщик",
+ "name": "Глибоке занурення",
"desc": "Середній результат 80%+ за 10+ квізів!",
"hint": "Тримай середній результат 80%+ у 10+ спробах"
}
|