From fd53b8d47e002afa85c1fe22950a7bb4b907f8d8 Mon Sep 17 00:00:00 2001 From: Thomas Starzynski Date: Thu, 29 Aug 2024 23:09:44 +0200 Subject: [PATCH 01/27] add methods to print analytics to console --- src/app/state/teamSlice.ts | 189 +++++++++++++++++-------------------- src/app/team/page.tsx | 17 ++-- 2 files changed, 95 insertions(+), 111 deletions(-) diff --git a/src/app/state/teamSlice.ts b/src/app/state/teamSlice.ts index e7719c11..2bd0c2ac 100644 --- a/src/app/state/teamSlice.ts +++ b/src/app/state/teamSlice.ts @@ -1,4 +1,4 @@ -import { createSlice } from "@reduxjs/toolkit"; +import { createSlice, PayloadAction } from "@reduxjs/toolkit"; import { GoogleSheet } from "../utils/GoogleSheet"; export interface TeamState { @@ -83,10 +83,10 @@ export interface Contributor { isActive?: boolean; phasesActive?: string[]; // analytics - tokensEarned?: number; - trophyGold?: number; - trophySilver?: number; - trophyBronze?: number; + tokensEarned: number; + trophyGold: number; + trophySilver: number; + trophyBronze: number; } export const teamSlice = createSlice({ @@ -94,7 +94,12 @@ export const teamSlice = createSlice({ initialState, // synchronous reducers - reducers: {}, + reducers: { + setTeamState: (state: TeamState, action: PayloadAction) => { + state.contributorMap = action.payload.contributorMap; + state.votingResultRows = action.payload.votingResultRows; + }, + }, // async thunks extraReducers: () => {}, @@ -113,61 +118,63 @@ export async function fetchTeamState(): Promise { }; } -export function showContributorRanking( - contributorMap: Map +export function showContributorTrophies( + contributorMap: [string, Contributor][] ) { - const contr = Array.from(contributorMap.entries()) - .map((arr): Contributor | undefined => arr[1]) + const contr = contributorMap + .map((arr) => arr[1]) + .filter((item): item is Contributor => item !== undefined) .filter( (item): item is Contributor => - item !== undefined && item.tokensEarned !== undefined + item.trophyGold > 0 || item.trophySilver > 0 || item.trophyBronze > 0 ) .sort( (a, b) => - (b as { tokensEarned: number }).tokensEarned - - (a as { tokensEarned: number }).tokensEarned + (b as { trophyGold: number }).trophyGold - + (a as { trophyGold: number }).trophyGold ); // eslint-disable-next-line no-console - console.log(contr.map((c) => `${c.telegram}: ${c.tokensEarned}`).join("\n")); + console.log(`# USER 🏆 | 🥈 | 🥉\n`); + // eslint-disable-next-line no-console + console.log( + contr + .map((c, indx, arr) => { + const maxNameLength = Math.max(...arr.map((c) => c.telegram.length)); // Get the max length of telegram names + const paddedName = c.telegram.padEnd(maxNameLength + 1); // Pad the names to align + const indexString = (indx + 1).toString().padStart(2); // Add a space for single-digit numbers + return `#${indexString} ${paddedName}: ${c.trophyGold} | ${c.trophySilver} | ${c.trophyBronze}`; // Combine everything into the final string + }) + .join("\n") + ); } -export function exportBarchartRaceData( - contributorMap: Map, - votingResultRows: VotingResultRow[] +export function showContributorTotalEarnings( + contributorMap: [string, Contributor][] ) { - const phasesArray = votingResultRows.map((row) => row.phase); - const uniqPhases = phasesArray - .filter((value, index, self) => self.indexOf(value) === index) - .sort((a, b) => a - b); - const finalRows = []; - const header = ["user", "imageUrl", "category"] - .concat(uniqPhases.map((num) => num.toString())) - .flat(); - finalRows.push(header); - // for each contributor - const uniqeUsers = Array.from(contributorMap.keys()); - for (let user of uniqeUsers) { - const contributor = contributorMap.get(user); - const userRow = [ - user, - contributor?.imageUrl || "", - contributor?.expertise[0], - ]; - let userTokens = 0; - const userVotingResultRows = votingResultRows.filter( - (row) => row.user === user + const contr = contributorMap + .map((arr) => arr[1]) + .filter((item): item is Contributor => item !== undefined) + .sort( + (a, b) => + (b as { tokensEarned: number }).tokensEarned - + (a as { tokensEarned: number }).tokensEarned ); - for (let phase = 1; phase <= 31; phase++) { - const targetUserVotingResultRow = userVotingResultRows.find( - (row) => row.phase === phase - ); - if (targetUserVotingResultRow && targetUserVotingResultRow.tokens) { - userTokens += targetUserVotingResultRow.tokens; - } - userRow.push(userTokens.toString()); - } - finalRows.push(userRow); - } + // eslint-disable-next-line no-console + console.log("TOTAL EARNINGS"); + // eslint-disable-next-line no-console + console.log( + contr + .map((c, indx, arr) => { + const maxNameLength = Math.max(...arr.map((c) => c.telegram.length)); // Get the max length of telegram names + const paddedName = c.telegram.padEnd(maxNameLength + 1); // Pad the names to align + const indexString = (indx + 1).toString().padStart(2); // Add a space for single-digit numbers + const valueString = Number(c.tokensEarned?.toFixed(0)) + .toLocaleString("en") + .padStart(8); // Right-align the value, assuming a max length of 7 digits plus comma formatting + return `#${indexString} ${paddedName}: ${valueString}`; // Combine everything into the final string + }) + .join("\n") + ); } function runContributorAnalytics( @@ -183,12 +190,6 @@ function runContributorAnalytics( } } -interface Trophies { - gold: { [key: string]: number }; - silver: { [key: string]: number }; - bronze: { [key: string]: number }; -} - function runPhaseAnalytics( phase: number, contributorMap: Map, @@ -197,40 +198,41 @@ function runPhaseAnalytics( if (phaseRows.length === 0) { return; } - // Total points - const total = phaseRows.map((row) => row.points).reduce((a, b) => a + b, 0); - // Run for all rows - const trophies: Trophies = { - gold: {}, - silver: {}, - bronze: {}, - }; + // Total points for this phase + const totalPoints = phaseRows + .map((row) => row.points) + .reduce((a, b) => a + b, 0); + // Total contributor rewards for this phase + const totalTokens = getAllocation(phase).contributors * getEmission(phase); + // Determine earned DEXTR tokens for each contributor in this phase for (let i = 0; i < phaseRows.length; i++) { const { user, points } = phaseRows[i]; + const contributor = contributorMap.get(user); + // Calculate trophies + if (!contributor) { + continue; + } + if (!points) { + console.error("Error with google sheet..."); + console.error({ phaseRows, phase, user, points }); + continue; + } if (i === 0) { - trophies.gold[user] = trophies.gold[user] ? trophies.gold[user] + 1 : 1; + contributor.trophyGold += 1; } if (i === 1) { - trophies.silver[user] = trophies.silver[user] - ? trophies.silver[user] + 1 - : 1; + contributor.trophySilver += 1; } if (i === 2) { - trophies.bronze[user] = trophies.bronze[user] - ? trophies.bronze[user] + 1 - : 1; - } - // get contributor rewards tokens - const totalTokens = getAllocation(phase).contributors * getEmission(phase); - const tokens = totalTokens * (points / total); - phaseRows[i].tokens = tokens; - const contributor = contributorMap.get(user); - if (!contributor) { - continue; + contributor.trophyBronze += 1; } + // Calculate user earnings per phase per user + const userEarnings = (points / totalPoints) * totalTokens; + phaseRows[i].tokens = userEarnings; + // contributor.tokensEarned += userEarnings; contributor.tokensEarned = contributor.tokensEarned - ? contributor.tokensEarned + tokens - : tokens; + ? contributor.tokensEarned + userEarnings + : userEarnings; } } @@ -245,6 +247,7 @@ async function fecthContributorMap(): Promise> { .map((row) => rowToContributor(row)); const contributorMap: Map = new Map(); for (let contributor of contributors) { + contributor.tokensEarned = 0; contributorMap.set(contributor.telegram, contributor); } return contributorMap; @@ -259,28 +262,6 @@ async function fetchVotingResultRows(): Promise { .split("\n") .slice(1) .map((row) => rowToVotingResultRow(row)); - // // compute tokens earned for each row - // for (let phase = 1; phase <= 224; phase++) { - // const phaseRows = votingResultRows.filter((row) => row.phase === phase); - // if (phaseRows.length === 0) { - // continue; - // } - // const phasePoints = phaseRows - // .map((row) => row.points) - // .reduce((a, b) => a + b, 0); - // const phaseContributorAllocation = - // getAllocation(phase).contributors * getEmission(phase); - // for (let phaseRow = 0; phaseRow < phaseRows.length; phaseRow++) { - // const { user, points } = phaseRows[phaseRow]; - // const contributor = contributorMap.get(user); - // if (!contributor) { - // continue; - // } - // contributor.tokensEarned = contributor.tokensEarned - // ? contributor.tokensEarned + userTokens - // : userTokens; - // } - // } } function rowToContributor(row: string): Contributor { @@ -296,6 +277,10 @@ function rowToContributor(row: string): Contributor { imageUrl, expertise, radixWallet, + tokensEarned: 0, + trophyGold: 0, + trophySilver: 0, + trophyBronze: 0, } as Contributor; } diff --git a/src/app/team/page.tsx b/src/app/team/page.tsx index fe511a17..41fca482 100644 --- a/src/app/team/page.tsx +++ b/src/app/team/page.tsx @@ -2,20 +2,19 @@ import { useEffect } from "react"; import { - fetchTeamState, - showContributorRanking, - Contributor, - exportBarchartRaceData, + showContributorTrophies, + showContributorTotalEarnings, + teamSlice, } from "state/teamSlice"; +import { store } from "state/store"; +import { fetchTeamState } from "state/teamSlice"; export default function Rewards() { useEffect(() => { fetchTeamState().then((teamState) => { - const contributorMap = new Map( - teamState.contributorMap - ); - showContributorRanking(contributorMap); - exportBarchartRaceData(contributorMap, teamState.votingResultRows); + store.dispatch(teamSlice.actions.setTeamState(teamState)); + showContributorTotalEarnings(teamState.contributorMap); + showContributorTrophies(teamState.contributorMap); }); }, []); From e5e06e528ea01104921798736c089f0ca343004d Mon Sep 17 00:00:00 2001 From: Thomas Starzynski Date: Fri, 30 Aug 2024 23:24:08 +0200 Subject: [PATCH 02/27] init translations for teams page --- src/app/state/i18nSlice.ts | 4 ++++ src/app/state/locales/en/team.json | 4 ++++ src/app/state/locales/pt/team.json | 4 ++++ src/app/team/page.tsx | 6 ++++-- 4 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 src/app/state/locales/en/team.json create mode 100644 src/app/state/locales/pt/team.json diff --git a/src/app/state/i18nSlice.ts b/src/app/state/i18nSlice.ts index fa561c0a..30d88f51 100644 --- a/src/app/state/i18nSlice.ts +++ b/src/app/state/i18nSlice.ts @@ -21,6 +21,7 @@ import enFooter from "./locales/en/footer.json"; import enLanding from "./locales/en/landing.json"; import enRewards from "./locales/en/rewards.json"; import enTrade from "./locales/en/trade.json"; +import enTeam from "./locales/en/team.json"; import ptEnums from "./locales/pt/enums.json"; import ptErrors from "./locales/pt/errors.json"; @@ -28,6 +29,7 @@ import ptFooter from "./locales/pt/footer.json"; import ptLanding from "./locales/pt/landing.json"; import ptRewards from "./locales/pt/rewards.json"; import ptTrade from "./locales/pt/trade.json"; +import ptTeam from "./locales/pt/team.json"; import { RootState } from "./store"; interface TextContent { @@ -59,6 +61,7 @@ const initialState: I18nState = { ...enLanding, ...enRewards, ...enTrade, + ...enTeam, }, pt: { ...ptEnums, @@ -67,6 +70,7 @@ const initialState: I18nState = { ...ptLanding, ...ptRewards, ...ptTrade, + ...ptTeam, }, }, }; diff --git a/src/app/state/locales/en/team.json b/src/app/state/locales/en/team.json new file mode 100644 index 00000000..dda76123 --- /dev/null +++ b/src/app/state/locales/en/team.json @@ -0,0 +1,4 @@ +{ + "meet_our_contributors": "Meet our contributors", + "we_have_a_diverse_talented": "We have a diverse, talented pool of contributors from various fields and locations worldwide. Anyone can join. Browse our current active or past members and connect with contributors to collaborate." +} diff --git a/src/app/state/locales/pt/team.json b/src/app/state/locales/pt/team.json new file mode 100644 index 00000000..7931c402 --- /dev/null +++ b/src/app/state/locales/pt/team.json @@ -0,0 +1,4 @@ +{ + "meet_our_contributors": "Conheça nossos colaboradores", + "we_have_a_diverse_talented": "Temos um grupo diversificado e talentoso de colaboradores de várias áreas e localizações ao redor do mundo. Qualquer pessoa pode participar. Explore nossos membros atuais ou passados e conecte-se com colaboradores para colaborar." +} diff --git a/src/app/team/page.tsx b/src/app/team/page.tsx index 41fca482..1bfb8122 100644 --- a/src/app/team/page.tsx +++ b/src/app/team/page.tsx @@ -1,6 +1,7 @@ "use client"; import { useEffect } from "react"; +import { useTranslations } from "hooks"; import { showContributorTrophies, showContributorTotalEarnings, @@ -29,10 +30,11 @@ export default function Rewards() { } function Contributors() { + const t = useTranslations(); return (
- - + +
); } From 0ff5cb632ef57f1851e6fdea7aded0d09eb7ab5f Mon Sep 17 00:00:00 2001 From: Thomas Starzynski Date: Fri, 30 Aug 2024 23:47:10 +0200 Subject: [PATCH 03/27] add header and filter section UI --- src/app/components/NavBar.tsx | 4 +++ src/app/state/locales/en/team.json | 1 + src/app/state/locales/pt/team.json | 1 + src/app/team/page.tsx | 45 ++++++++++++++++++++++++++---- src/app/utils/GoogleSheet.ts | 15 ++++++++++ 5 files changed, 61 insertions(+), 5 deletions(-) create mode 100644 src/app/utils/GoogleSheet.ts diff --git a/src/app/components/NavBar.tsx b/src/app/components/NavBar.tsx index 34052a07..64d178f0 100644 --- a/src/app/components/NavBar.tsx +++ b/src/app/components/NavBar.tsx @@ -43,6 +43,10 @@ const NavItems: { path: string; title: string }[] = [ path: "/rewards", title: "rewards", }, + { + path: "/team", + title: "team", + }, { path: "/roadmap", title: "roadmap", diff --git a/src/app/state/locales/en/team.json b/src/app/state/locales/en/team.json index dda76123..910d9b53 100644 --- a/src/app/state/locales/en/team.json +++ b/src/app/state/locales/en/team.json @@ -1,4 +1,5 @@ { + "team": "Team", "meet_our_contributors": "Meet our contributors", "we_have_a_diverse_talented": "We have a diverse, talented pool of contributors from various fields and locations worldwide. Anyone can join. Browse our current active or past members and connect with contributors to collaborate." } diff --git a/src/app/state/locales/pt/team.json b/src/app/state/locales/pt/team.json index 7931c402..1c2d06f2 100644 --- a/src/app/state/locales/pt/team.json +++ b/src/app/state/locales/pt/team.json @@ -1,4 +1,5 @@ { + "team": "Equipe", "meet_our_contributors": "Conheça nossos colaboradores", "we_have_a_diverse_talented": "Temos um grupo diversificado e talentoso de colaboradores de várias áreas e localizações ao redor do mundo. Qualquer pessoa pode participar. Explore nossos membros atuais ou passados e conecte-se com colaboradores para colaborar." } diff --git a/src/app/team/page.tsx b/src/app/team/page.tsx index 1bfb8122..b24d7a24 100644 --- a/src/app/team/page.tsx +++ b/src/app/team/page.tsx @@ -21,26 +21,61 @@ export default function Rewards() { return (
-
- {/* */} +
+ +
); } -function Contributors() { +function Filters() { + return ( +
+

Activity status

+ + + +

Area of work

+ + + + + + +
+ ); +} + +function FilterToggle({ label }: { label: string }) { + return ( +
+

{label}

+
+ ); +} + +function HeaderComponent() { const t = useTranslations(); return ( -
+
); } +function Contributors() { + return
; +} + function DexterParagraph({ text }: { text: string }) { - return

{text}

; + return ( +
+

{text}

+
+ ); } function DexterHeading({ title }: { title: string }) { diff --git a/src/app/utils/GoogleSheet.ts b/src/app/utils/GoogleSheet.ts new file mode 100644 index 00000000..0f08ac3a --- /dev/null +++ b/src/app/utils/GoogleSheet.ts @@ -0,0 +1,15 @@ +/** + * This Service loads data from a google sheet as single string in CSV format + */ +const fetchSheet = async (sheetId: string, gic: string) => { + const url = `https://docs.google.com/spreadsheets/d/${sheetId}/export?format=csv&gid=${gic}`; + const response = await fetch(url); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + return await response.text(); // Getting the CSV data as text +}; + +export const GoogleSheet = { + fetch: fetchSheet, +}; From ebbeca433e914fe55434e5d198431df677a3f2be Mon Sep 17 00:00:00 2001 From: Thomas Starzynski Date: Fri, 30 Aug 2024 23:53:35 +0200 Subject: [PATCH 04/27] save hover animation on filterToggle --- src/app/team/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/team/page.tsx b/src/app/team/page.tsx index b24d7a24..d83ce266 100644 --- a/src/app/team/page.tsx +++ b/src/app/team/page.tsx @@ -50,7 +50,7 @@ function Filters() { function FilterToggle({ label }: { label: string }) { return ( -
+

{label}

); From 2014740be1c6d9526eac9ed804d520a5d9ad617e Mon Sep 17 00:00:00 2001 From: Thomas Starzynski Date: Sat, 31 Aug 2024 00:49:43 +0200 Subject: [PATCH 05/27] compute active contributors (+show) --- src/app/state/store.ts | 2 ++ src/app/state/teamSlice.ts | 45 +++++++++++++++++++++++++++++++------- src/app/team/page.tsx | 2 ++ 3 files changed, 41 insertions(+), 8 deletions(-) diff --git a/src/app/state/store.ts b/src/app/state/store.ts index 24f84c4d..b587e4de 100644 --- a/src/app/state/store.ts +++ b/src/app/state/store.ts @@ -8,6 +8,7 @@ import { radixSlice } from "./radixSlice"; import { rewardSlice } from "./rewardSlice"; import { priceInfoSlice } from "./priceInfoSlice"; import { i18nSlice } from "./i18nSlice"; +import { teamSlice } from "./teamSlice"; export const store = configureStore({ reducer: { @@ -20,6 +21,7 @@ export const store = configureStore({ priceInfo: priceInfoSlice.reducer, i18n: i18nSlice.reducer, rewardSlice: rewardSlice.reducer, + teamSlice: teamSlice.reducer, }, }); diff --git a/src/app/state/teamSlice.ts b/src/app/state/teamSlice.ts index 2bd0c2ac..0ea97d0d 100644 --- a/src/app/state/teamSlice.ts +++ b/src/app/state/teamSlice.ts @@ -80,7 +80,7 @@ export interface Contributor { // badges isOG?: boolean; isLongTerm?: boolean; - isActive?: boolean; + isActive: boolean; phasesActive?: string[]; // analytics tokensEarned: number; @@ -177,17 +177,45 @@ export function showContributorTotalEarnings( ); } +export function showActiveContributors( + contributorMap: [string, Contributor][] +) { + const activeContributors = contributorMap + .map((arr) => arr[1]) + .filter((contributor) => contributor.isActive); + // eslint-disable-next-line no-console + console.log(activeContributors); + // eslint-disable-next-line no-console + console.log(activeContributors.length); +} + function runContributorAnalytics( contributorMap: Map, votingResultRows: VotingResultRow[] ): void { - for (let phase = 1; phase <= 224; phase++) { + const lastPhase = votingResultRows[votingResultRows.length - 1].phase; + for (let phase = 1; phase <= lastPhase; phase++) { const phaseRows = votingResultRows.filter((row) => row.phase === phase); if (phaseRows.length === 0) { continue; } runPhaseAnalytics(phase, contributorMap, phaseRows); } + // mark contributor activity level + const nLastPhases = 3; // contributing withing the last 3 phases + const activePhaseThreshold = lastPhase - (nLastPhases - 1); // last phase also counts + const votingResultRowsSubset = votingResultRows.filter( + (row) => row.phase >= activePhaseThreshold + ); + const activeUsers = votingResultRowsSubset + .map((row) => row.user) + .filter((val, indx, self) => self.indexOf(val) === indx); + for (const username of activeUsers) { + const contributor = contributorMap.get(username); + if (contributor) { + contributor.isActive = true; + } + } } function runPhaseAnalytics( @@ -205,8 +233,8 @@ function runPhaseAnalytics( // Total contributor rewards for this phase const totalTokens = getAllocation(phase).contributors * getEmission(phase); // Determine earned DEXTR tokens for each contributor in this phase - for (let i = 0; i < phaseRows.length; i++) { - const { user, points } = phaseRows[i]; + for (let row = 0; row < phaseRows.length; row++) { + const { user, points } = phaseRows[row]; const contributor = contributorMap.get(user); // Calculate trophies if (!contributor) { @@ -217,18 +245,18 @@ function runPhaseAnalytics( console.error({ phaseRows, phase, user, points }); continue; } - if (i === 0) { + if (row === 0) { contributor.trophyGold += 1; } - if (i === 1) { + if (row === 1) { contributor.trophySilver += 1; } - if (i === 2) { + if (row === 2) { contributor.trophyBronze += 1; } // Calculate user earnings per phase per user const userEarnings = (points / totalPoints) * totalTokens; - phaseRows[i].tokens = userEarnings; + phaseRows[row].tokens = userEarnings; // contributor.tokensEarned += userEarnings; contributor.tokensEarned = contributor.tokensEarned ? contributor.tokensEarned + userEarnings @@ -275,6 +303,7 @@ function rowToContributor(row: string): Contributor { github: github.toLowerCase(), discord: discord.toLowerCase(), imageUrl, + isActive: false, expertise, radixWallet, tokensEarned: 0, diff --git a/src/app/team/page.tsx b/src/app/team/page.tsx index d83ce266..e22f3ff0 100644 --- a/src/app/team/page.tsx +++ b/src/app/team/page.tsx @@ -6,6 +6,7 @@ import { showContributorTrophies, showContributorTotalEarnings, teamSlice, + showActiveContributors, } from "state/teamSlice"; import { store } from "state/store"; import { fetchTeamState } from "state/teamSlice"; @@ -16,6 +17,7 @@ export default function Rewards() { store.dispatch(teamSlice.actions.setTeamState(teamState)); showContributorTotalEarnings(teamState.contributorMap); showContributorTrophies(teamState.contributorMap); + showActiveContributors(teamState.contributorMap); }); }, []); From 180bb48f36cb2200b286415d1f2484f99c3ef65f Mon Sep 17 00:00:00 2001 From: Thomas Starzynski Date: Sat, 31 Aug 2024 00:50:35 +0200 Subject: [PATCH 06/27] fix copy-past typo component name --- src/app/team/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/team/page.tsx b/src/app/team/page.tsx index e22f3ff0..c45b649f 100644 --- a/src/app/team/page.tsx +++ b/src/app/team/page.tsx @@ -11,7 +11,7 @@ import { import { store } from "state/store"; import { fetchTeamState } from "state/teamSlice"; -export default function Rewards() { +export default function Team() { useEffect(() => { fetchTeamState().then((teamState) => { store.dispatch(teamSlice.actions.setTeamState(teamState)); From 402260e17bbfa94f6ef90c2b18905b0f9cc8616d Mon Sep 17 00:00:00 2001 From: Thomas Starzynski Date: Sat, 31 Aug 2024 00:56:01 +0200 Subject: [PATCH 07/27] fix trophy sorting bug --- src/app/state/teamSlice.ts | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/src/app/state/teamSlice.ts b/src/app/state/teamSlice.ts index 0ea97d0d..3fc4925a 100644 --- a/src/app/state/teamSlice.ts +++ b/src/app/state/teamSlice.ts @@ -128,11 +128,18 @@ export function showContributorTrophies( (item): item is Contributor => item.trophyGold > 0 || item.trophySilver > 0 || item.trophyBronze > 0 ) - .sort( - (a, b) => - (b as { trophyGold: number }).trophyGold - - (a as { trophyGold: number }).trophyGold - ); + .sort((a, b) => { + if (b.trophyGold !== a.trophyGold) { + // First, sort by gold trophies + return b.trophyGold - a.trophyGold; + } else if (b.trophySilver !== a.trophySilver) { + // Then, sort by silver trophies + return b.trophySilver - a.trophySilver; + } else { + // Finally, sort by bronze trophies + return b.trophyBronze - a.trophyBronze; + } + }); // eslint-disable-next-line no-console console.log(`# USER 🏆 | 🥈 | 🥉\n`); // eslint-disable-next-line no-console From 4aa34cec35c08ea00a5d9ff2c4df9888ffcffd71 Mon Sep 17 00:00:00 2001 From: Thomas Starzynski Date: Sat, 31 Aug 2024 03:02:31 +0200 Subject: [PATCH 08/27] update enums + expertise --- src/app/components/NavBar.tsx | 8 ++++---- src/app/state/locales/en/enums.json | 9 ++++++++- src/app/state/locales/pt/enums.json | 9 ++++++++- src/app/state/teamSlice.ts | 15 ++++++++++----- 4 files changed, 30 insertions(+), 11 deletions(-) diff --git a/src/app/components/NavBar.tsx b/src/app/components/NavBar.tsx index 64d178f0..02b0ac07 100644 --- a/src/app/components/NavBar.tsx +++ b/src/app/components/NavBar.tsx @@ -43,14 +43,14 @@ const NavItems: { path: string; title: string }[] = [ path: "/rewards", title: "rewards", }, - { - path: "/team", - title: "team", - }, { path: "/roadmap", title: "roadmap", }, + { + path: "/team", + title: "team", + }, ]; export function Navbar() { diff --git a/src/app/state/locales/en/enums.json b/src/app/state/locales/en/enums.json index de6a0143..60046fde 100644 --- a/src/app/state/locales/en/enums.json +++ b/src/app/state/locales/en/enums.json @@ -12,5 +12,12 @@ "ORDER_HISTORY": "Order history", "TRADE_HISTORY": "Trade history", "PAY": "You pay", - "RECEIVE": "You receive" + "RECEIVE": "You receive", + "ADMIN": "Admin", + "DEV": "Dev", + "DESIGN": "Design", + "SOCIAL_MEDIA": "Social media", + "TESTING": "Testing", + "ACTIVE": "Active", + "PAST": "Past" } diff --git a/src/app/state/locales/pt/enums.json b/src/app/state/locales/pt/enums.json index dc0eea67..c8ce5e27 100644 --- a/src/app/state/locales/pt/enums.json +++ b/src/app/state/locales/pt/enums.json @@ -12,5 +12,12 @@ "ORDER_HISTORY": "Histórico de ordens", "TRADE_HISTORY": "Histórico de transações", "PAY": "Você paga", - "RECEIVE": "Você recebe" + "RECEIVE": "Você recebe", + "ADMIN": "Admin", + "DEV": "Dev", + "DESIGN": "Design", + "SOCIAL_MEDIA": "Mídias sociais", + "TESTING": "Testes", + "ACTIVE": "Ativo", + "PAST": "Passado" } diff --git a/src/app/state/teamSlice.ts b/src/app/state/teamSlice.ts index 3fc4925a..9c3ed86c 100644 --- a/src/app/state/teamSlice.ts +++ b/src/app/state/teamSlice.ts @@ -62,12 +62,16 @@ function getEmission(phase: number): number { } export enum Expertise { - "DEVELOPER" = "DEVELOPER", + "ADMIN" = "ADMIN", + "DEV" = "DEV", "DESIGN" = "DESIGN", "SOCIAL_MEDIA" = "SOCIAL_MEDIA", - "ADMINISTRATION" = "ADMINISTRATION", - "TESTER" = "TESTER", - "NA" = "NA", + "TESTING" = "TESTING", +} + +export enum ActivityStatus { + "ACTIVE" = "ACTIVE", + "PAST" = "PAST", } export interface Contributor { @@ -302,8 +306,9 @@ async function fetchVotingResultRows(): Promise { function rowToContributor(row: string): Contributor { const [telegram, github, discord, imageUrl, expertiseStr, radixWallet] = row.split(","); - const expertise = expertiseStr + const expertise: Expertise[] = expertiseStr .split(";") + .filter((str) => Object.values(Expertise).includes(str as Expertise)) .map((str) => str.toUpperCase() as Expertise); return { telegram: telegram.toLowerCase(), From e83904063240b7d961768b884b3946790ede3d1b Mon Sep 17 00:00:00 2001 From: Thomas Starzynski Date: Sat, 31 Aug 2024 03:30:46 +0200 Subject: [PATCH 09/27] add filtering logic --- src/app/state/teamSlice.ts | 46 +++++++++++++++++++++++------------ src/app/team/page.tsx | 50 +++++++++++++++++++++++++++++--------- 2 files changed, 69 insertions(+), 27 deletions(-) diff --git a/src/app/state/teamSlice.ts b/src/app/state/teamSlice.ts index 9c3ed86c..1ab4f93c 100644 --- a/src/app/state/teamSlice.ts +++ b/src/app/state/teamSlice.ts @@ -4,6 +4,8 @@ import { GoogleSheet } from "../utils/GoogleSheet"; export interface TeamState { contributorMap: [string, Contributor][]; votingResultRows: VotingResultRow[]; + activityStatusFilter?: ActivityStatus; + expertiseFilter?: Expertise; } interface VotingResultRow { @@ -13,9 +15,24 @@ interface VotingResultRow { tokens?: number; } +export enum Expertise { + "ADMIN" = "ADMIN", + "DEV" = "DEV", + "DESIGN" = "DESIGN", + "SOCIAL_MEDIA" = "SOCIAL_MEDIA", + "TESTING" = "TESTING", +} + +export enum ActivityStatus { + "ACTIVE" = "ACTIVE", + "PAST" = "PAST", +} + const initialState: TeamState = { contributorMap: [], votingResultRows: [], + activityStatusFilter: ActivityStatus.ACTIVE, + // expertiseFilter -> unset be default = show all }; interface Allocation { @@ -61,25 +78,12 @@ function getEmission(phase: number): number { : 0; } -export enum Expertise { - "ADMIN" = "ADMIN", - "DEV" = "DEV", - "DESIGN" = "DESIGN", - "SOCIAL_MEDIA" = "SOCIAL_MEDIA", - "TESTING" = "TESTING", -} - -export enum ActivityStatus { - "ACTIVE" = "ACTIVE", - "PAST" = "PAST", -} - export interface Contributor { telegram: string; github?: string; discord?: string; imageUrl?: string; - expertise?: Expertise[]; + expertise: Expertise[]; radixWallet?: string; // badges isOG?: boolean; @@ -103,6 +107,18 @@ export const teamSlice = createSlice({ state.contributorMap = action.payload.contributorMap; state.votingResultRows = action.payload.votingResultRows; }, + setExpertiseFilter: ( + state: TeamState, + action: PayloadAction + ) => { + state.expertiseFilter = action.payload; + }, + setActivityStatusFilter: ( + state: TeamState, + action: PayloadAction + ) => { + state.activityStatusFilter = action.payload; + }, }, // async thunks @@ -212,7 +228,7 @@ function runContributorAnalytics( } runPhaseAnalytics(phase, contributorMap, phaseRows); } - // mark contributor activity level + // mark contributor Status level const nLastPhases = 3; // contributing withing the last 3 phases const activePhaseThreshold = lastPhase - (nLastPhases - 1); // last phase also counts const votingResultRowsSubset = votingResultRows.filter( diff --git a/src/app/team/page.tsx b/src/app/team/page.tsx index c45b649f..55f8c21b 100644 --- a/src/app/team/page.tsx +++ b/src/app/team/page.tsx @@ -1,12 +1,14 @@ "use client"; import { useEffect } from "react"; -import { useTranslations } from "hooks"; +import { useAppDispatch, useAppSelector, useTranslations } from "hooks"; import { showContributorTrophies, showContributorTotalEarnings, teamSlice, showActiveContributors, + ActivityStatus, + Expertise, } from "state/teamSlice"; import { store } from "state/store"; import { fetchTeamState } from "state/teamSlice"; @@ -36,23 +38,47 @@ function Filters() { return (

Activity status

- - - + + +

Area of work

- - - - - - + + + + + +
); } -function FilterToggle({ label }: { label: string }) { +function ActivityStatusToggle({ filter }: { filter?: ActivityStatus }) { + const { activityStatusFilter } = useAppSelector((state) => state.teamSlice); + const label = filter === undefined ? "ALL" : filter; + const isActive = activityStatusFilter === filter; return ( -
+
+

{label}

+
+ ); +} + +function ExpertiseToggle({ filter }: { filter?: Expertise }) { + const dispatch = useAppDispatch(); + const { expertiseFilter } = useAppSelector((state) => state.teamSlice); + const label = filter === undefined ? "ALL" : filter; + const isActive = expertiseFilter === filter; + return ( +
dispatch(teamSlice.actions.setExpertiseFilter(filter))} + >

{label}

); From 6689c9ebbf1d8290818fa53024c106d504294b18 Mon Sep 17 00:00:00 2001 From: Thomas Starzynski Date: Sat, 31 Aug 2024 03:37:48 +0200 Subject: [PATCH 10/27] fix expertise filter not working --- src/app/state/teamSlice.ts | 2 +- src/app/team/page.tsx | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/src/app/state/teamSlice.ts b/src/app/state/teamSlice.ts index 1ab4f93c..f24a1491 100644 --- a/src/app/state/teamSlice.ts +++ b/src/app/state/teamSlice.ts @@ -32,7 +32,7 @@ const initialState: TeamState = { contributorMap: [], votingResultRows: [], activityStatusFilter: ActivityStatus.ACTIVE, - // expertiseFilter -> unset be default = show all + expertiseFilter: undefined, }; interface Allocation { diff --git a/src/app/team/page.tsx b/src/app/team/page.tsx index 55f8c21b..1949f896 100644 --- a/src/app/team/page.tsx +++ b/src/app/team/page.tsx @@ -53,6 +53,7 @@ function Filters() { } function ActivityStatusToggle({ filter }: { filter?: ActivityStatus }) { + const dispatch = useAppDispatch(); const { activityStatusFilter } = useAppSelector((state) => state.teamSlice); const label = filter === undefined ? "ALL" : filter; const isActive = activityStatusFilter === filter; @@ -61,6 +62,9 @@ function ActivityStatusToggle({ filter }: { filter?: ActivityStatus }) { className={`cursor-pointer inline-block mx-1 my-3 px-6 py-3 bg-black opacity-25 rounded-badge hover:opacity-100 ${ isActive ? "!opacity-100" : "" }`} + onClick={() => + dispatch(teamSlice.actions.setActivityStatusFilter(filter)) + } >

{label}

From 4f86054e94a175bd8704fe14de3feb2f05da8672 Mon Sep 17 00:00:00 2001 From: Thomas Date: Sat, 31 Aug 2024 15:00:19 +0200 Subject: [PATCH 11/27] update filter UI --- src/app/team/page.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/app/team/page.tsx b/src/app/team/page.tsx index 1949f896..9e899f73 100644 --- a/src/app/team/page.tsx +++ b/src/app/team/page.tsx @@ -59,14 +59,14 @@ function ActivityStatusToggle({ filter }: { filter?: ActivityStatus }) { const isActive = activityStatusFilter === filter; return (
dispatch(teamSlice.actions.setActivityStatusFilter(filter)) } > -

{label}

+

{label}

); } @@ -78,12 +78,12 @@ function ExpertiseToggle({ filter }: { filter?: Expertise }) { const isActive = expertiseFilter === filter; return (
dispatch(teamSlice.actions.setExpertiseFilter(filter))} > -

{label}

+

{label}

); } From f0acadbd76d277aab7e3b513c1d516bff13caffb Mon Sep 17 00:00:00 2001 From: Thomas Date: Sun, 1 Sep 2024 01:02:06 +0200 Subject: [PATCH 12/27] add contributor cards V0 --- src/app/state/locales/en/team.json | 5 +- src/app/state/locales/pt/team.json | 5 +- src/app/state/teamSlice.ts | 8 ++- src/app/team/page.tsx | 91 +++++++++++++++++++++++++----- 4 files changed, 93 insertions(+), 16 deletions(-) diff --git a/src/app/state/locales/en/team.json b/src/app/state/locales/en/team.json index 910d9b53..4a49c9e8 100644 --- a/src/app/state/locales/en/team.json +++ b/src/app/state/locales/en/team.json @@ -1,5 +1,8 @@ { "team": "Team", "meet_our_contributors": "Meet our contributors", - "we_have_a_diverse_talented": "We have a diverse, talented pool of contributors from various fields and locations worldwide. Anyone can join. Browse our current active or past members and connect with contributors to collaborate." + "we_have_a_diverse_talented": "We have a diverse, talented pool of contributors from various fields and locations worldwide. Anyone can join. Browse our current active or past members and connect with contributors to collaborate.", + "all": "All", + "activity_status": "Activity status", + "area_of_work": "Area of work" } diff --git a/src/app/state/locales/pt/team.json b/src/app/state/locales/pt/team.json index 1c2d06f2..feb7d0ac 100644 --- a/src/app/state/locales/pt/team.json +++ b/src/app/state/locales/pt/team.json @@ -1,5 +1,8 @@ { "team": "Equipe", "meet_our_contributors": "Conheça nossos colaboradores", - "we_have_a_diverse_talented": "Temos um grupo diversificado e talentoso de colaboradores de várias áreas e localizações ao redor do mundo. Qualquer pessoa pode participar. Explore nossos membros atuais ou passados e conecte-se com colaboradores para colaborar." + "we_have_a_diverse_talented": "Temos um grupo diversificado e talentoso de colaboradores de várias áreas e localizações ao redor do mundo. Qualquer pessoa pode participar. Explore nossos membros atuais ou passados e conecte-se com colaboradores para colaborar.", + "all": "Todos", + "activity_status": "Status da atividade", + "area_of_work": "Área de trabalho" } diff --git a/src/app/state/teamSlice.ts b/src/app/state/teamSlice.ts index f24a1491..fd93de83 100644 --- a/src/app/state/teamSlice.ts +++ b/src/app/state/teamSlice.ts @@ -79,6 +79,7 @@ function getEmission(phase: number): number { } export interface Contributor { + name: string; telegram: string; github?: string; discord?: string; @@ -89,7 +90,7 @@ export interface Contributor { isOG?: boolean; isLongTerm?: boolean; isActive: boolean; - phasesActive?: string[]; + phasesActive: number[]; // analytics tokensEarned: number; trophyGold: number; @@ -272,6 +273,9 @@ function runPhaseAnalytics( console.error({ phaseRows, phase, user, points }); continue; } + // Add phase to phasesActive array + contributor.phasesActive.push(phase); + // Add trophies if (row === 0) { contributor.trophyGold += 1; } @@ -327,6 +331,7 @@ function rowToContributor(row: string): Contributor { .filter((str) => Object.values(Expertise).includes(str as Expertise)) .map((str) => str.toUpperCase() as Expertise); return { + name: telegram, telegram: telegram.toLowerCase(), github: github.toLowerCase(), discord: discord.toLowerCase(), @@ -334,6 +339,7 @@ function rowToContributor(row: string): Contributor { isActive: false, expertise, radixWallet, + phasesActive: [], tokensEarned: 0, trophyGold: 0, trophySilver: 0, diff --git a/src/app/team/page.tsx b/src/app/team/page.tsx index 9e899f73..513cb99c 100644 --- a/src/app/team/page.tsx +++ b/src/app/team/page.tsx @@ -9,6 +9,7 @@ import { showActiveContributors, ActivityStatus, Expertise, + Contributor, } from "state/teamSlice"; import { store } from "state/store"; import { fetchTeamState } from "state/teamSlice"; @@ -24,8 +25,8 @@ export default function Team() { }, []); return ( -
-
+
+
@@ -35,13 +36,14 @@ export default function Team() { } function Filters() { + const t = useTranslations(); return (
-

Activity status

+

{t("activity_status")}

-

Area of work

+

{t("area_of_work")}

@@ -53,37 +55,39 @@ function Filters() { } function ActivityStatusToggle({ filter }: { filter?: ActivityStatus }) { + const t = useTranslations(); const dispatch = useAppDispatch(); const { activityStatusFilter } = useAppSelector((state) => state.teamSlice); - const label = filter === undefined ? "ALL" : filter; + const label = filter === undefined ? "all" : filter; const isActive = activityStatusFilter === filter; return (
dispatch(teamSlice.actions.setActivityStatusFilter(filter)) } > -

{label}

+

{t(label)}

); } function ExpertiseToggle({ filter }: { filter?: Expertise }) { + const t = useTranslations(); const dispatch = useAppDispatch(); const { expertiseFilter } = useAppSelector((state) => state.teamSlice); - const label = filter === undefined ? "ALL" : filter; + const label = filter === undefined ? "all" : filter; const isActive = expertiseFilter === filter; return (
dispatch(teamSlice.actions.setExpertiseFilter(filter))} > -

{label}

+

{t(label)}

); } @@ -99,7 +103,68 @@ function HeaderComponent() { } function Contributors() { - return
; + const { contributorMap } = useAppSelector((state) => state.teamSlice); + const contributors = contributorMap + .map((arr) => arr[1]) + .sort((a, b) => b.phasesActive.length - a.phasesActive.length); + return ( +
+ {contributors.map((contributor, indx) => { + return ; + })} +
+ ); +} + +// TODO: Expertise +// TODO: links to social channels +// TODO: active badge +function ContributorCard({ contributor }: { contributor: Contributor }) { + return ( +
+ {/* Flexbox container for image and text */} +
+ {/* Contributor Image */} + {contributor.telegram} + + {/* Contributor Details */} +
+ {/* Truncate telegram name */} +

+ {contributor.name} +

+ {/* Display ADMIN and OG on the same line */} +

+ ADMIN + OG +

+

+ contributed in {contributor.phasesActive.length} phases +

+
+
+
+

+ {contributor.telegram} +

+

+ {contributor.github} +

+

+ {contributor.discord} +

+
+
+ ); } function DexterParagraph({ text }: { text: string }) { From 3e9f38aa0cf953d2cdfa32f11a0c9acc77cae3c4 Mon Sep 17 00:00:00 2001 From: Thomas Date: Sun, 1 Sep 2024 01:21:02 +0200 Subject: [PATCH 13/27] small adaptations --- src/app/team/page.tsx | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/app/team/page.tsx b/src/app/team/page.tsx index 513cb99c..00c15a97 100644 --- a/src/app/team/page.tsx +++ b/src/app/team/page.tsx @@ -106,6 +106,7 @@ function Contributors() { const { contributorMap } = useAppSelector((state) => state.teamSlice); const contributors = contributorMap .map((arr) => arr[1]) + .filter((c) => c.isActive) .sort((a, b) => b.phasesActive.length - a.phasesActive.length); return (
@@ -121,7 +122,14 @@ function Contributors() { // TODO: active badge function ContributorCard({ contributor }: { contributor: Contributor }) { return ( -
+
+ {contributor.isActive && ( +
+

+ Active +

+
+ )} {/* Flexbox container for image and text */}
{/* Contributor Image */} @@ -129,17 +137,18 @@ function ContributorCard({ contributor }: { contributor: Contributor }) { src={ contributor.imageUrl || "https://dexternominations.space/_next/image?url=%2Fcontimg%2Fdefault.jpg&w=256&q=75" + // "grey-circle.svg" } alt={contributor.telegram} - width="60" - height="60" - className="rounded-full" + width="55" + height="55" + className={`rounded-full ${contributor.imageUrl ? "" : "opacity-80"}`} /> {/* Contributor Details */}
{/* Truncate telegram name */} -

+

{contributor.name}

{/* Display ADMIN and OG on the same line */} From a5d38569758c8e2d93ad6c661183df82405bb048 Mon Sep 17 00:00:00 2001 From: Thomas Date: Sun, 1 Sep 2024 15:37:25 +0200 Subject: [PATCH 14/27] add OG badge to all contributors of phase 1-3 --- src/app/state/teamSlice.ts | 42 ++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/src/app/state/teamSlice.ts b/src/app/state/teamSlice.ts index fd93de83..3ec5848c 100644 --- a/src/app/state/teamSlice.ts +++ b/src/app/state/teamSlice.ts @@ -87,7 +87,7 @@ export interface Contributor { expertise: Expertise[]; radixWallet?: string; // badges - isOG?: boolean; + isOG: boolean; isLongTerm?: boolean; isActive: boolean; phasesActive: number[]; @@ -229,20 +229,31 @@ function runContributorAnalytics( } runPhaseAnalytics(phase, contributorMap, phaseRows); } - // mark contributor Status level - const nLastPhases = 3; // contributing withing the last 3 phases - const activePhaseThreshold = lastPhase - (nLastPhases - 1); // last phase also counts - const votingResultRowsSubset = votingResultRows.filter( - (row) => row.phase >= activePhaseThreshold - ); - const activeUsers = votingResultRowsSubset - .map((row) => row.user) - .filter((val, indx, self) => self.indexOf(val) === indx); - for (const username of activeUsers) { - const contributor = contributorMap.get(username); - if (contributor) { - contributor.isActive = true; - } + // mark contributors that are active + { + const nLastPhases = 3; // contributing withing the last 3 phases + const activePhaseThreshold = lastPhase - (nLastPhases - 1); // last phase also counts + const votingResultRowsSubset = votingResultRows.filter( + (row) => row.phase >= activePhaseThreshold + ); + votingResultRowsSubset.forEach((row) => { + const contributor = contributorMap.get(row.user); + if (contributor) { + contributor.isActive = true; + } + }); + } + // mark OG contributors + { + const votingResultRowsSubset = votingResultRows.filter( + (row) => row.phase <= 3 + ); + votingResultRowsSubset.forEach((row) => { + const contributor = contributorMap.get(row.user); + if (contributor) { + contributor.isOG = true; + } + }); } } @@ -337,6 +348,7 @@ function rowToContributor(row: string): Contributor { discord: discord.toLowerCase(), imageUrl, isActive: false, + isOG: false, expertise, radixWallet, phasesActive: [], From c1a3bd831bc250f2773cd07eb207fbe9d4ec15a4 Mon Sep 17 00:00:00 2001 From: Thomas Date: Sun, 1 Sep 2024 15:38:47 +0200 Subject: [PATCH 15/27] add social links + badges --- src/app/team/page.tsx | 158 ++++++++++++++++++++++++++++++++---------- 1 file changed, 122 insertions(+), 36 deletions(-) diff --git a/src/app/team/page.tsx b/src/app/team/page.tsx index 00c15a97..c97e9657 100644 --- a/src/app/team/page.tsx +++ b/src/app/team/page.tsx @@ -13,6 +13,8 @@ import { } from "state/teamSlice"; import { store } from "state/store"; import { fetchTeamState } from "state/teamSlice"; +import { DexterButton } from "components/DexterButton"; +import { FaTelegram, FaDiscord, FaGithub } from "react-icons/fa"; export default function Team() { useEffect(() => { @@ -30,11 +32,22 @@ export default function Team() { +
); } +function HeaderComponent() { + const t = useTranslations(); + return ( +
+ + +
+ ); +} + function Filters() { const t = useTranslations(); return ( @@ -92,16 +105,6 @@ function ExpertiseToggle({ filter }: { filter?: Expertise }) { ); } -function HeaderComponent() { - const t = useTranslations(); - return ( -
- - -
- ); -} - function Contributors() { const { contributorMap } = useAppSelector((state) => state.teamSlice); const contributors = contributorMap @@ -109,18 +112,22 @@ function Contributors() { .filter((c) => c.isActive) .sort((a, b) => b.phasesActive.length - a.phasesActive.length); return ( -
- {contributors.map((contributor, indx) => { - return ; - })} +
+

+ {contributors.length} contributors found +

+
+ {contributors.map((contributor, indx) => { + return ; + })} +
); } -// TODO: Expertise // TODO: links to social channels -// TODO: active badge function ContributorCard({ contributor }: { contributor: Contributor }) { + const t = useTranslations(); return (
{contributor.isActive && ( @@ -140,8 +147,8 @@ function ContributorCard({ contributor }: { contributor: Contributor }) { // "grey-circle.svg" } alt={contributor.telegram} - width="55" - height="55" + width="60" + height="60" className={`rounded-full ${contributor.imageUrl ? "" : "opacity-80"}`} /> @@ -151,31 +158,104 @@ function ContributorCard({ contributor }: { contributor: Contributor }) {

{contributor.name}

- {/* Display ADMIN and OG on the same line */} -

- ADMIN - OG -

-

+ {/* Display badges */} +

+ {contributor.expertise.map((expertise, indx) => { + return ; + })} + {contributor.isOG && } +
+

contributed in {contributor.phasesActive.length} phases

-
-

- {contributor.telegram} -

-

- {contributor.github} -

-

- {contributor.discord} -

+ {/* Social Links */} +
+ {contributor.telegram && ( + + )} + {contributor.github && ( + + )} + {contributor.discord && ( + + )}
); } +enum SocialPlatform { + "TELEGRAM" = "TELEGRAM", + "GITHUB" = "GITHUB", + "DISCORD" = "DISCORD", +} +function SocialLink({ + username, + socialPlatform, +}: { + username: string; + socialPlatform: SocialPlatform; +}) { + const { iconHtml, url } = + socialPlatform === SocialPlatform.TELEGRAM + ? { + iconHtml: , + url: `https://t.me/${username.toLowerCase()}`, + } + : socialPlatform === SocialPlatform.GITHUB + ? { + iconHtml: , + url: `https://github.com/${username.toLowerCase()}`, + } + : socialPlatform === SocialPlatform.DISCORD + ? { + iconHtml: , + url: `https://t.me/${username.toLowerCase()}`, + } + : { + iconHtml: <>, + url: "", + }; + return ( + + {iconHtml} + + ); +} + +function Badge({ text }: { text: string }) { + const color = + text === "OG" ? "bg-[#00ca92] text-black font-bold" : "bg-[#191B1D]"; + return ( +
{text}
+ ); +} + +function JoinUs() { + return ( +
+ + + +
+ ); +} + function DexterParagraph({ text }: { text: string }) { return (
@@ -184,7 +264,13 @@ function DexterParagraph({ text }: { text: string }) { ); } -function DexterHeading({ title }: { title: string }) { +function DexterHeading({ + title, + fontSize, +}: { + title: string; + fontSize?: number; +}) { return ( <>

{title} From 319c446743d5dc5a87887d2e651d52a7584d1295 Mon Sep 17 00:00:00 2001 From: Thomas Date: Sun, 1 Sep 2024 15:47:38 +0200 Subject: [PATCH 16/27] remove discord as no links possible + add hover text over truncated elements --- src/app/team/page.tsx | 26 ++++++++++++-------------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/src/app/team/page.tsx b/src/app/team/page.tsx index c97e9657..9635e0c5 100644 --- a/src/app/team/page.tsx +++ b/src/app/team/page.tsx @@ -155,7 +155,10 @@ function ContributorCard({ contributor }: { contributor: Contributor }) { {/* Contributor Details */}
{/* Truncate telegram name */} -

+

12 ? contributor.name : ""} + > {contributor.name}

{/* Display badges */} @@ -184,12 +187,6 @@ function ContributorCard({ contributor }: { contributor: Contributor }) { socialPlatform={SocialPlatform.GITHUB} /> )} - {contributor.discord && ( - - )}

); @@ -198,8 +195,9 @@ function ContributorCard({ contributor }: { contributor: Contributor }) { enum SocialPlatform { "TELEGRAM" = "TELEGRAM", "GITHUB" = "GITHUB", - "DISCORD" = "DISCORD", + // "DISCORD" = "DISCORD", } + function SocialLink({ username, socialPlatform, @@ -218,12 +216,12 @@ function SocialLink({ iconHtml: , url: `https://github.com/${username.toLowerCase()}`, } - : socialPlatform === SocialPlatform.DISCORD - ? { - iconHtml: , - url: `https://t.me/${username.toLowerCase()}`, - } - : { + : // : socialPlatform === SocialPlatform.DISCORD + // ? { + // iconHtml: , + // url: `https://t.me/${username.toLowerCase()}`, + // } + { iconHtml: <>, url: "", }; From 6c3978ec6da025c5997f1d1c5701e26024396a5b Mon Sep 17 00:00:00 2001 From: Thomas Date: Sun, 1 Sep 2024 16:20:16 +0200 Subject: [PATCH 17/27] add selector to filter contributors --- src/app/state/teamSlice.ts | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/src/app/state/teamSlice.ts b/src/app/state/teamSlice.ts index 3ec5848c..6c54ffb0 100644 --- a/src/app/state/teamSlice.ts +++ b/src/app/state/teamSlice.ts @@ -1,4 +1,4 @@ -import { createSlice, PayloadAction } from "@reduxjs/toolkit"; +import { createSlice, createSelector, PayloadAction } from "@reduxjs/toolkit"; import { GoogleSheet } from "../utils/GoogleSheet"; export interface TeamState { @@ -367,3 +367,37 @@ function rowToVotingResultRow(row: string): VotingResultRow { points: Number(points), } as VotingResultRow; } + +export const selectFilteredContributors = createSelector( + (state: TeamState) => state.contributorMap, // Get the contributor map from the state + (state: TeamState) => state.activityStatusFilter, // Get the activity status filter from the state + (state: TeamState) => state.expertiseFilter, // Get the expertise filter from the state + ( + contributorMap: [string, Contributor][], + activityStatusFilter: ActivityStatus | undefined, + expertiseFilter: Expertise | undefined + ): Contributor[] => { + // Transform contributorMap into an array of Contributor objects + let contributors = contributorMap.map(([, contributor]) => contributor); + + // Apply activity status filter if it is set + if (activityStatusFilter !== undefined) { + contributors = contributors.filter( + (contributor) => + (activityStatusFilter === ActivityStatus.ACTIVE && + contributor.isActive) || + (activityStatusFilter === ActivityStatus.PAST && + !contributor.isActive) + ); + } + + // Apply expertise filter if it is set + if (expertiseFilter !== undefined) { + contributors = contributors.filter((contributor) => + contributor.expertise.includes(expertiseFilter) + ); + } + + return contributors; // Return the filtered array of contributors + } +); From 825862e6d8c89b120765cc8394367c2a4b01de5d Mon Sep 17 00:00:00 2001 From: Thomas Date: Sun, 1 Sep 2024 16:20:37 +0200 Subject: [PATCH 18/27] implement selector to filter contributors --- src/app/team/page.tsx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/app/team/page.tsx b/src/app/team/page.tsx index 9635e0c5..49de0acd 100644 --- a/src/app/team/page.tsx +++ b/src/app/team/page.tsx @@ -10,11 +10,13 @@ import { ActivityStatus, Expertise, Contributor, + selectFilteredContributors, + TeamState, } from "state/teamSlice"; import { store } from "state/store"; import { fetchTeamState } from "state/teamSlice"; import { DexterButton } from "components/DexterButton"; -import { FaTelegram, FaDiscord, FaGithub } from "react-icons/fa"; +import { FaTelegram, FaGithub } from "react-icons/fa"; export default function Team() { useEffect(() => { @@ -106,11 +108,8 @@ function ExpertiseToggle({ filter }: { filter?: Expertise }) { } function Contributors() { - const { contributorMap } = useAppSelector((state) => state.teamSlice); - const contributors = contributorMap - .map((arr) => arr[1]) - .filter((c) => c.isActive) - .sort((a, b) => b.phasesActive.length - a.phasesActive.length); + const { teamSlice } = useAppSelector((state) => state); + const contributors = selectFilteredContributors(teamSlice); return (

From e292a50b4f61e06c50e098730c4d67f0a9f2906f Mon Sep 17 00:00:00 2001 From: Thomas Date: Sun, 1 Sep 2024 19:07:43 +0200 Subject: [PATCH 19/27] implement filtering --- src/app/state/teamSlice.ts | 69 +++++++++++++++++++++++++++++--------- src/app/team/page.tsx | 23 +++---------- 2 files changed, 58 insertions(+), 34 deletions(-) diff --git a/src/app/state/teamSlice.ts b/src/app/state/teamSlice.ts index 6c54ffb0..05c967c4 100644 --- a/src/app/state/teamSlice.ts +++ b/src/app/state/teamSlice.ts @@ -1,4 +1,9 @@ -import { createSlice, createSelector, PayloadAction } from "@reduxjs/toolkit"; +import { + createSlice, + createSelector, + PayloadAction, + createAsyncThunk, +} from "@reduxjs/toolkit"; import { GoogleSheet } from "../utils/GoogleSheet"; export interface TeamState { @@ -6,6 +11,8 @@ export interface TeamState { votingResultRows: VotingResultRow[]; activityStatusFilter?: ActivityStatus; expertiseFilter?: Expertise; + isLoading: boolean; + isError: boolean; } interface VotingResultRow { @@ -33,6 +40,8 @@ const initialState: TeamState = { votingResultRows: [], activityStatusFilter: ActivityStatus.ACTIVE, expertiseFilter: undefined, + isLoading: false, + isError: false, }; interface Allocation { @@ -123,22 +132,30 @@ export const teamSlice = createSlice({ }, // async thunks - extraReducers: () => {}, + extraReducers: (builder) => { + builder + .addCase(fetchTeamState.pending, (state) => { + state.isLoading = true; + state.isError = false; + }) + .addCase( + fetchTeamState.fulfilled, + (state, action: PayloadAction) => { + // Update state with the fetched team data + state.isLoading = false; + state.contributorMap = action.payload.contributorMap; + state.votingResultRows = action.payload.votingResultRows; + } + ) + .addCase(fetchTeamState.rejected, (state, action) => { + // Handle errors if needed + console.error("Failed to fetch team state:", action.error); + state.isLoading = false; + state.isError = true; + }); + }, }); -export async function fetchTeamState(): Promise { - const [contributorMap, votingResultRows] = await Promise.all([ - fecthContributorMap(), - fetchVotingResultRows(), - ]); - // Compute contributor analytics - runContributorAnalytics(contributorMap, votingResultRows); - return { - contributorMap: Array.from(contributorMap.entries()), - votingResultRows, - }; -} - export function showContributorTrophies( contributorMap: [string, Contributor][] ) { @@ -401,3 +418,25 @@ export const selectFilteredContributors = createSelector( return contributors; // Return the filtered array of contributors } ); + +interface FetchTeamStatePayload { + contributorMap: [string, Contributor][]; + votingResultRows: VotingResultRow[]; +} + +// Async thunk for fetching team state +export const fetchTeamState = createAsyncThunk( + "team/fetchTeamState", + async (): Promise => { + const [contributorMap, votingResultRows] = await Promise.all([ + fecthContributorMap(), + fetchVotingResultRows(), + ]); + // Compute contributor analytics + runContributorAnalytics(contributorMap, votingResultRows); + return { + contributorMap: Array.from(contributorMap.entries()), + votingResultRows, + }; + } +); diff --git a/src/app/team/page.tsx b/src/app/team/page.tsx index 49de0acd..5265f7b0 100644 --- a/src/app/team/page.tsx +++ b/src/app/team/page.tsx @@ -3,30 +3,21 @@ import { useEffect } from "react"; import { useAppDispatch, useAppSelector, useTranslations } from "hooks"; import { - showContributorTrophies, - showContributorTotalEarnings, teamSlice, - showActiveContributors, ActivityStatus, Expertise, Contributor, selectFilteredContributors, - TeamState, } from "state/teamSlice"; -import { store } from "state/store"; import { fetchTeamState } from "state/teamSlice"; import { DexterButton } from "components/DexterButton"; import { FaTelegram, FaGithub } from "react-icons/fa"; export default function Team() { + const dispatch = useAppDispatch(); useEffect(() => { - fetchTeamState().then((teamState) => { - store.dispatch(teamSlice.actions.setTeamState(teamState)); - showContributorTotalEarnings(teamState.contributorMap); - showContributorTrophies(teamState.contributorMap); - showActiveContributors(teamState.contributorMap); - }); - }, []); + dispatch(fetchTeamState()); + }, [dispatch]); return (

@@ -194,7 +185,6 @@ function ContributorCard({ contributor }: { contributor: Contributor }) { enum SocialPlatform { "TELEGRAM" = "TELEGRAM", "GITHUB" = "GITHUB", - // "DISCORD" = "DISCORD", } function SocialLink({ @@ -215,12 +205,7 @@ function SocialLink({ iconHtml: , url: `https://github.com/${username.toLowerCase()}`, } - : // : socialPlatform === SocialPlatform.DISCORD - // ? { - // iconHtml: , - // url: `https://t.me/${username.toLowerCase()}`, - // } - { + : { iconHtml: <>, url: "", }; From 4b75fc885d1a02c4bf966fa842535fe90ce34aad Mon Sep 17 00:00:00 2001 From: Thomas Date: Sun, 1 Sep 2024 23:11:14 +0200 Subject: [PATCH 20/27] sort contributors by impact --- src/app/state/teamSlice.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/app/state/teamSlice.ts b/src/app/state/teamSlice.ts index 05c967c4..b530ef72 100644 --- a/src/app/state/teamSlice.ts +++ b/src/app/state/teamSlice.ts @@ -415,7 +415,8 @@ export const selectFilteredContributors = createSelector( ); } - return contributors; // Return the filtered array of contributors + // Show contributors with highest impact first + return [...contributors].sort((a, b) => b.tokensEarned - a.tokensEarned); } ); From 6925551fbcf0c090cdd00039c1176ac8cc5d0baf Mon Sep 17 00:00:00 2001 From: Thomas Date: Sun, 1 Sep 2024 23:36:49 +0200 Subject: [PATCH 21/27] add loading state using skeleton --- src/app/state/teamSlice.ts | 2 +- src/app/team/page.tsx | 48 +++++++++++++++++++++++++++----------- 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/src/app/state/teamSlice.ts b/src/app/state/teamSlice.ts index b530ef72..5f9720a2 100644 --- a/src/app/state/teamSlice.ts +++ b/src/app/state/teamSlice.ts @@ -40,7 +40,7 @@ const initialState: TeamState = { votingResultRows: [], activityStatusFilter: ActivityStatus.ACTIVE, expertiseFilter: undefined, - isLoading: false, + isLoading: true, isError: false, }; diff --git a/src/app/team/page.tsx b/src/app/team/page.tsx index 5265f7b0..377d2a5e 100644 --- a/src/app/team/page.tsx +++ b/src/app/team/page.tsx @@ -12,6 +12,7 @@ import { import { fetchTeamState } from "state/teamSlice"; import { DexterButton } from "components/DexterButton"; import { FaTelegram, FaGithub } from "react-icons/fa"; +import { SkeletonRectangle } from "components/Skeleton"; export default function Team() { const dispatch = useAppDispatch(); @@ -68,7 +69,7 @@ function ActivityStatusToggle({ filter }: { filter?: ActivityStatus }) { const isActive = activityStatusFilter === filter; return (
@@ -88,7 +89,7 @@ function ExpertiseToggle({ filter }: { filter?: Expertise }) { const isActive = expertiseFilter === filter; return (
dispatch(teamSlice.actions.setExpertiseFilter(filter))} @@ -103,19 +104,40 @@ function Contributors() { const contributors = selectFilteredContributors(teamSlice); return (
-

- {contributors.length} contributors found -

-
- {contributors.map((contributor, indx) => { - return ; - })} -
+ {teamSlice.isLoading ? ( + <> +
+ +
+
+ {[1, 2, 3, 4, 5, 6, 7, 8].map((_, indx) => { + return ( + + ); + })} +
+ + ) : ( + <> +

+ {contributors.length} contributors found +

+
+ {contributors.map((contributor, indx) => { + return ; + })} +
+ + )}
); } -// TODO: links to social channels function ContributorCard({ contributor }: { contributor: Contributor }) { const t = useTranslations(); return ( @@ -197,12 +219,12 @@ function SocialLink({ const { iconHtml, url } = socialPlatform === SocialPlatform.TELEGRAM ? { - iconHtml: , + iconHtml: , url: `https://t.me/${username.toLowerCase()}`, } : socialPlatform === SocialPlatform.GITHUB ? { - iconHtml: , + iconHtml: , url: `https://github.com/${username.toLowerCase()}`, } : { From 84f9993b7954b6a2bd1708b80c8a07b2c8689415 Mon Sep 17 00:00:00 2001 From: Thomas Date: Sun, 1 Sep 2024 23:58:38 +0200 Subject: [PATCH 22/27] add translations --- src/app/state/locales/en/team.json | 7 ++++++- src/app/state/locales/pt/team.json | 7 ++++++- src/app/team/page.tsx | 18 +++++++++++++----- 3 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/app/state/locales/en/team.json b/src/app/state/locales/en/team.json index 4a49c9e8..2efe5c87 100644 --- a/src/app/state/locales/en/team.json +++ b/src/app/state/locales/en/team.json @@ -4,5 +4,10 @@ "we_have_a_diverse_talented": "We have a diverse, talented pool of contributors from various fields and locations worldwide. Anyone can join. Browse our current active or past members and connect with contributors to collaborate.", "all": "All", "activity_status": "Activity status", - "area_of_work": "Area of work" + "area_of_work": "Area of work", + "N_contributors_found": "<$NUMBER> contributors found", + "want_to_join_us": "Want to join us?", + "we_are_always_looking_for": "We are always looking for talented contributors.", + "register_now": "Register now", + "contributed_in_N_phases": "contributed in <$NUMBER> phases" } diff --git a/src/app/state/locales/pt/team.json b/src/app/state/locales/pt/team.json index feb7d0ac..942a2ca1 100644 --- a/src/app/state/locales/pt/team.json +++ b/src/app/state/locales/pt/team.json @@ -4,5 +4,10 @@ "we_have_a_diverse_talented": "Temos um grupo diversificado e talentoso de colaboradores de várias áreas e localizações ao redor do mundo. Qualquer pessoa pode participar. Explore nossos membros atuais ou passados e conecte-se com colaboradores para colaborar.", "all": "Todos", "activity_status": "Status da atividade", - "area_of_work": "Área de trabalho" + "area_of_work": "Área de trabalho", + "N_contributors_found": "<$NUMBER> contributors found", + "want_to_join_us": "Quer se juntar a nós?", + "we_are_always_looking_for": "Estamos sempre à procura de colaboradores talentosos.", + "register_now": "Registre-se agora", + "contributed_in_N_phases": "contribuiu em <$NUMBER> fases" } diff --git a/src/app/team/page.tsx b/src/app/team/page.tsx index 377d2a5e..e54051ed 100644 --- a/src/app/team/page.tsx +++ b/src/app/team/page.tsx @@ -100,6 +100,7 @@ function ExpertiseToggle({ filter }: { filter?: Expertise }) { } function Contributors() { + const t = useTranslations(); const { teamSlice } = useAppSelector((state) => state); const contributors = selectFilteredContributors(teamSlice); return ( @@ -125,7 +126,10 @@ function Contributors() { ) : ( <>

- {contributors.length} contributors found + {t("N_contributors_found").replaceAll( + "<$NUMBER>", + contributors.length.toString() + )}

{contributors.map((contributor, indx) => { @@ -181,7 +185,10 @@ function ContributorCard({ contributor }: { contributor: Contributor }) { {contributor.isOG && }

- contributed in {contributor.phasesActive.length} phases + {t("contributed_in_N_phases").replaceAll( + "<$NUMBER>", + contributor.phasesActive.length.toString() + )}

@@ -247,12 +254,13 @@ function Badge({ text }: { text: string }) { } function JoinUs() { + const t = useTranslations(); return (
- - + + From d575788adf4c78306ac2ff3043e2fdf68f3a4301 Mon Sep 17 00:00:00 2001 From: Thomas Date: Mon, 2 Sep 2024 20:49:42 +0200 Subject: [PATCH 23/27] fix roadmap translation --- src/app/state/locales/pt/footer.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/state/locales/pt/footer.json b/src/app/state/locales/pt/footer.json index 5312a1ca..324b33be 100644 --- a/src/app/state/locales/pt/footer.json +++ b/src/app/state/locales/pt/footer.json @@ -18,5 +18,5 @@ "report_bug": "Relatar erro", "support": "Suporte", "report_translation_issue": "Relatar problema de tradução", - "roadmap": "Mapa rodoviário" + "roadmap": "Roadmap" } From 9dee5e38de05d7e39eb6a1ae5c1ac07038a6722b Mon Sep 17 00:00:00 2001 From: Thomas Date: Mon, 2 Sep 2024 22:22:48 +0200 Subject: [PATCH 24/27] add spacing --- src/app/team/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/team/page.tsx b/src/app/team/page.tsx index e54051ed..edbfc5f2 100644 --- a/src/app/team/page.tsx +++ b/src/app/team/page.tsx @@ -50,7 +50,7 @@ function Filters() { -

{t("area_of_work")}

+

{t("area_of_work")}

From 14360bb1a5ad3eeec4f3789d0bf69b98e5ea3477 Mon Sep 17 00:00:00 2001 From: Thomas Date: Mon, 2 Sep 2024 22:40:30 +0200 Subject: [PATCH 25/27] replace hardcoded colors with configured colors --- src/app/team/page.tsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/app/team/page.tsx b/src/app/team/page.tsx index edbfc5f2..9ab170f5 100644 --- a/src/app/team/page.tsx +++ b/src/app/team/page.tsx @@ -21,7 +21,7 @@ export default function Team() { }, [dispatch]); return ( -
+
@@ -69,7 +69,7 @@ function ActivityStatusToggle({ filter }: { filter?: ActivityStatus }) { const isActive = activityStatusFilter === filter; return (
@@ -89,7 +89,7 @@ function ExpertiseToggle({ filter }: { filter?: Expertise }) { const isActive = expertiseFilter === filter; return (
dispatch(teamSlice.actions.setExpertiseFilter(filter))} @@ -145,7 +145,7 @@ function Contributors() { function ContributorCard({ contributor }: { contributor: Contributor }) { const t = useTranslations(); return ( -
+
{contributor.isActive && (

@@ -247,7 +247,7 @@ function SocialLink({ function Badge({ text }: { text: string }) { const color = - text === "OG" ? "bg-[#00ca92] text-black font-bold" : "bg-[#191B1D]"; + text === "OG" ? "bg-[#00ca92] text-black font-bold" : "bg-base-200"; return (

{text}
); From 4de239a1cb1706316e8b7e1101a5b7470b12873b Mon Sep 17 00:00:00 2001 From: Thomas Date: Mon, 2 Sep 2024 22:49:52 +0200 Subject: [PATCH 26/27] fix 'selector leading to unnecessary rerenders' bug --- src/app/state/teamSlice.ts | 7 ++++--- src/app/team/page.tsx | 6 +++--- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/app/state/teamSlice.ts b/src/app/state/teamSlice.ts index 5f9720a2..44bf0cfb 100644 --- a/src/app/state/teamSlice.ts +++ b/src/app/state/teamSlice.ts @@ -5,6 +5,7 @@ import { createAsyncThunk, } from "@reduxjs/toolkit"; import { GoogleSheet } from "../utils/GoogleSheet"; +import { RootState } from "./store"; export interface TeamState { contributorMap: [string, Contributor][]; @@ -386,9 +387,9 @@ function rowToVotingResultRow(row: string): VotingResultRow { } export const selectFilteredContributors = createSelector( - (state: TeamState) => state.contributorMap, // Get the contributor map from the state - (state: TeamState) => state.activityStatusFilter, // Get the activity status filter from the state - (state: TeamState) => state.expertiseFilter, // Get the expertise filter from the state + (state: RootState) => state.teamSlice.contributorMap, // Get the contributor map from the state + (state: RootState) => state.teamSlice.activityStatusFilter, // Get the activity status filter from the state + (state: RootState) => state.teamSlice.expertiseFilter, // Get the expertise filter from the state ( contributorMap: [string, Contributor][], activityStatusFilter: ActivityStatus | undefined, diff --git a/src/app/team/page.tsx b/src/app/team/page.tsx index 9ab170f5..a35e14cd 100644 --- a/src/app/team/page.tsx +++ b/src/app/team/page.tsx @@ -101,11 +101,11 @@ function ExpertiseToggle({ filter }: { filter?: Expertise }) { function Contributors() { const t = useTranslations(); - const { teamSlice } = useAppSelector((state) => state); - const contributors = selectFilteredContributors(teamSlice); + const { isLoading } = useAppSelector((state) => state.teamSlice); + const contributors = useAppSelector(selectFilteredContributors); return (
- {teamSlice.isLoading ? ( + {isLoading ? ( <>
From 4bda6948026a539cdd0ba6e37c24338ec379eeea Mon Sep 17 00:00:00 2001 From: Thomas Date: Mon, 2 Sep 2024 23:02:40 +0200 Subject: [PATCH 27/27] add capability to change display name --- src/app/state/teamSlice.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/src/app/state/teamSlice.ts b/src/app/state/teamSlice.ts index 44bf0cfb..824b614d 100644 --- a/src/app/state/teamSlice.ts +++ b/src/app/state/teamSlice.ts @@ -353,14 +353,21 @@ async function fetchVotingResultRows(): Promise { } function rowToContributor(row: string): Contributor { - const [telegram, github, discord, imageUrl, expertiseStr, radixWallet] = - row.split(","); + const [ + telegram, + displayName, + github, + discord, + imageUrl, + expertiseStr, + radixWallet, + ] = row.split(","); const expertise: Expertise[] = expertiseStr .split(";") .filter((str) => Object.values(Expertise).includes(str as Expertise)) .map((str) => str.toUpperCase() as Expertise); return { - name: telegram, + name: displayName || telegram, telegram: telegram.toLowerCase(), github: github.toLowerCase(), discord: discord.toLowerCase(),