From 939d80eb3d91b0371ff115c3dbf4051a48a0d707 Mon Sep 17 00:00:00 2001 From: CluEleSsUK Date: Mon, 27 Nov 2023 14:30:26 +0000 Subject: [PATCH] Prettified Code! --- .../recipient-selection/drawCard.tsx | 228 +++++++++-------- .../recipient-selection/explainer.tsx | 241 ++++++++++-------- .../transparency/recipient-selection/page.tsx | 97 ++++--- .../transparency/recipient-selection/state.ts | 86 +++---- 4 files changed, 334 insertions(+), 318 deletions(-) diff --git a/website/src/app/[lang]/[region]/(website)/transparency/recipient-selection/drawCard.tsx b/website/src/app/[lang]/[region]/(website)/transparency/recipient-selection/drawCard.tsx index 899f10bec..81f07fc85 100644 --- a/website/src/app/[lang]/[region]/(website)/transparency/recipient-selection/drawCard.tsx +++ b/website/src/app/[lang]/[region]/(website)/transparency/recipient-selection/drawCard.tsx @@ -1,136 +1,138 @@ 'use client'; -import {FutureDraw, PastDraw} from '@/app/[lang]/[region]/(website)/transparency/recipient-selection/state'; -import {Disclosure} from '@headlessui/react'; -import {ChevronDownIcon, ChevronUpIcon} from '@heroicons/react/20/solid'; -import {Translator} from '@socialincome/shared/src/utils/i18n'; -import {Card, Typography} from '@socialincome/ui'; +import { FutureDraw, PastDraw } from '@/app/[lang]/[region]/(website)/transparency/recipient-selection/state'; +import { Disclosure } from '@headlessui/react'; +import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/react/20/solid'; +import { Translator } from '@socialincome/shared/src/utils/i18n'; +import { Card, Typography } from '@socialincome/ui'; import * as React from 'react'; export interface DrawCardsProps { - summary: React.ReactNode; - detail?: React.ReactNode; + summary: React.ReactNode; + detail?: React.ReactNode; } // a collapsible element rendering the summary, and optionally a button to open the detailed view export function DrawCard(props: DrawCardsProps) { - return ( - - {({open}) => ( - -
-
{props.summary}
-
- {!!props.detail ? ( - - {open ? ( - - ) : ( - - )} - - ) : ( - /* this is a filthy hack for spacing, but is actually responsive by some sorcery */ -           - )} -
-
- {!!props.detail && {props.detail}} -
- )} -
- ); + return ( + + {({ open }) => ( + +
+
{props.summary}
+
+ {!!props.detail ? ( + + {open ? ( + + ) : ( + + )} + + ) : ( + /* this is a filthy hack for spacing, but is actually responsive by some sorcery */ +           + )} +
+
+ {!!props.detail && {props.detail}} +
+ )} +
+ ); } // this details the fields that are in each draw's card export function DrawHeader() { - return ( -
-
- - Date - -
-
- - Draw name - -
-
- - Details - -
-
- ) + return ( +
+
+ + Date + +
+
+ + Draw name + +
+
+ + Details + +
+
+ ); } export type DrawSummaryProps = { - translations: { - from: string; - }; - draw: PastDraw | FutureDraw; - translator?: Translator; + translations: { + from: string; + }; + draw: PastDraw | FutureDraw; + translator?: Translator; }; -export function DrawSummary({draw, translations}: DrawSummaryProps) { - return ( -
-
- - {new Date(draw.time).toDateString()} - -
-
- - {draw.name} - -
-
- - {draw.count} {translations.from} {draw.total} - -
-
- ); +export function DrawSummary({ draw, translations }: DrawSummaryProps) { + return ( +
+
+ + {new Date(draw.time).toDateString()} + +
+
+ + {draw.name} + +
+
+ + {draw.count} {translations.from} {draw.total} + +
+
+ ); } export type DrawDetailProps = { - translations: { - randomNumber: string; - longlist: string; - people: string; - confirmDrand: string; - confirmGithub: string; - }; - draw: PastDraw; + translations: { + randomNumber: string; + longlist: string; + people: string; + confirmDrand: string; + confirmGithub: string; + }; + draw: PastDraw; }; -export function DrawDetail({draw, translations}: DrawDetailProps) { - return ( -
-
-
- {translations.randomNumber}: - {draw.drandRandomness} -
-
- - {translations.confirmDrand} - -
-
-
-
- {translations.people}: - {translations.longlist} -
-
- - {translations.confirmGithub} - -
-
-
- ); +export function DrawDetail({ draw, translations }: DrawDetailProps) { + return ( +
+
+
+ {translations.randomNumber}: + {draw.drandRandomness} +
+
+ + {translations.confirmDrand} + +
+
+
+
+ {translations.people}: + {translations.longlist} +
+
+ + {translations.confirmGithub} + +
+
+
+ ); } diff --git a/website/src/app/[lang]/[region]/(website)/transparency/recipient-selection/explainer.tsx b/website/src/app/[lang]/[region]/(website)/transparency/recipient-selection/explainer.tsx index b8ec48de4..966c5a5c2 100644 --- a/website/src/app/[lang]/[region]/(website)/transparency/recipient-selection/explainer.tsx +++ b/website/src/app/[lang]/[region]/(website)/transparency/recipient-selection/explainer.tsx @@ -1,120 +1,135 @@ -import {Card, CardContent, Typography} from "@socialincome/ui" +import { Card, CardContent, Typography } from '@socialincome/ui'; export function Explainer() { - return ( -
- - Selecting Recipients - -

Social Income, like any UBI project, has limited financial resources. Choosing who gets help and who - doesn't is tough. We want everyone to understand how we select our recipients. Since November 2023, - we’ve employed a new way to randomly select people from poor communities, thanks to our partnership - with drand.

-
- - Our selection process in 3 steps: - -
- - Step 1: Finding potential recipients - -

We team up with a variety of international and local NGOs who support marginalized communities and are - familiar with poverty at the local level. After visiting and positively assessing these NGOs – as well - as the areas and communities they support – we may request that they provide us with a list of all - potential recipients.

-

Potential recipients are people living in poverty with whom they maintain direct - contact. Depending on the communities supported, those lists can include anything from 100 to 1000 - names.

+ return ( +
+ + Selecting Recipients + +

+ Social Income, like any UBI project, has limited financial resources. Choosing who gets help and who doesn't + is tough. We want everyone to understand how we select our recipients. Since November 2023, we’ve employed a new + way to randomly select people from poor communities, thanks to our partnership with{' '} + drand. +

+
+ + Our selection process in 3 steps: + +
+ + Step 1: Finding potential recipients + +

+ We team up with a variety of international and local NGOs who support marginalized communities and are familiar + with poverty at the local level. After visiting and positively assessing these NGOs – as well as the areas and + communities they support – we may request that they provide us with a list of all potential recipients. +

+

+ Potential recipients are people living in poverty with whom they maintain direct contact. Depending on the + communities supported, those lists can include anything from 100 to 1000 names. +

-
- - Step 2: Selecting at random and with utmost transparency - -

The lists we are - working with during selection contain minimal information about potential recipients. They are - subsequently - anonymized (hashed) and uploaded to our open-source - repository. With the help of a random number generated and published by drand, a mechanism is set in place to select a predetermined - number of individuals from the list. This process can be mathematically traced back and verified without - compromising recipients’ data.

- - -
-

- 🔎 Upcoming: allowing everyone to easily verify for themselves whether or not they're on one - of the NGOs' lists or whether they have been chosen. -

-
-
-
- - Step 3: Communicating transparently - -

After a draw, we reach out to both the NGO and the community directly to share the results. The - beneficiaries who were selected – upon confirming their participation – then provide additional - information and are onboarded for our 3-year program

-
- - Frequently asked questions - -
- - Why do we select randomly out of a pool of qualified recipients? - -

The purpose of employing this random selection method is to achieve several objectives. Firstly, it helps - prevent bias in our recipient selection, ensuring that relatives or acquaintances of individuals - involved in the process are not given preferential treatment. Secondly, it aims to avoid tensions - between recipients and non-recipients, as well as any potential conflicts between recipients and our - organization. Lastly, we strive to incorporate technology where it is feasible and beneficial to the - process.

+
+ + Step 2: Selecting at random and with utmost transparency + +

+ The lists we are + working with during selection contain minimal information about potential recipients. They are subsequently + anonymized (hashed) and uploaded to our{' '} + open-source repository. With the help of a random + number generated and published by drand, a mechanism is set in place to + select a predetermined number of individuals from the list. This process can be mathematically traced back and + verified without compromising recipients’ data. +

+ + +
+

+ 🔎 Upcoming: allowing everyone to easily verify for themselves whether or not they're on one of the + NGOs' lists or whether they have been chosen. +

+
+
+
+ + Step 3: Communicating transparently + +

+ After a draw, we reach out to both the NGO and the community directly to share the results. The beneficiaries + who were selected – upon confirming their participation – then provide additional information and are onboarded + for our 3-year program +

+
+ + Frequently asked questions + +
+ + Why do we select randomly out of a pool of qualified recipients? + +

+ The purpose of employing this random selection method is to achieve several objectives. Firstly, it helps + prevent bias in our recipient selection, ensuring that relatives or acquaintances of individuals involved in the + process are not given preferential treatment. Secondly, it aims to avoid tensions between recipients and + non-recipients, as well as any potential conflicts between recipients and our organization. Lastly, we strive to + incorporate technology where it is feasible and beneficial to the process. +

-
- - Who can influence the draft? - -

By relying on an unpredictable random value from drand, set to be emitted in the future, it's not - possible for anyone to sway the results of the selection process. This inherent randomness can be - utilized to confirm the integrity of the selection. To qualify for Social Income, an individual must - first be on a list provided by an NGO. While this does present a preliminary condition, it aligns with - our foundational principle: ensuring that Social Income reaches those most in need.

+
+ + Who can influence the draft? + +

+ By relying on an unpredictable random value from drand, set to be emitted in the future, it's not possible + for anyone to sway the results of the selection process. This inherent randomness can be utilized to confirm the + integrity of the selection. To qualify for Social Income, an individual must first be on a list provided by an + NGO. While this does present a preliminary condition, it aligns with our foundational principle: ensuring that + Social Income reaches those most in need. +

-
- - How do we avoid bias and tensions? - -

To ensure we don't inadvertently prioritize a specific ethnic group, gender, or occupation, we - collaborate with diverse NGOs that have varied missions. In line with our 'do no harm' policy, - we strive to prevent tensions among recipients, non-recipients, communities, and the NGOs. Our random - selection plays a pivotal role in achieving this. It ensures that those related to or acquainted with - individuals in the process aren't shown favoritism, which could lead to conflicts.

+
+ + How do we avoid bias and tensions? + +

+ To ensure we don't inadvertently prioritize a specific ethnic group, gender, or occupation, we collaborate + with diverse NGOs that have varied missions. In line with our 'do no harm' policy, we strive to prevent + tensions among recipients, non-recipients, communities, and the NGOs. Our random selection plays a pivotal role + in achieving this. It ensures that those related to or acquainted with individuals in the process aren't + shown favoritism, which could lead to conflicts. +

-
- - What is happening during the selection process? - -

We utilize the random number provided by drand, which is publicly accessible, to pick a set number of - individuals from our list. Here's how it's done: we first organize the hashed list of - recipients. Then, using the randomness element from drand, we convert it into a position on the list - through a key derivation function. - For those keen on the technical details, the function and its associated processes are documented in - our Github repository.

+
+ + What is happening during the selection process? + +

+ We utilize the random number provided by drand, which is publicly accessible, to pick a set number of + individuals from our list. Here's how it's done: we first organize the hashed list of recipients. Then, + using the randomness element from drand, we convert it into a position on the list through a{' '} + key derivation function. For those keen on + the technical details, the function and its associated processes are documented in our{' '} + Github repository. +

-
- - What is drand and who is behind it? - -

The drand project generates random numbers that everyone can trust. It can be applied to create truly - random and verifiable drafts. Initiated in 2017 at EPFL by Nicolas Gailly, drand received support from - Philipp Jovanovic and was guided by Bryan Ford. It has since become independently managed and maintains - a network called the League of Entropy with - EPFL, UCL, Cloudflare, Kudelski Security, the University of Chile, and Protocol Labs. Current core - maintainers of the open source project are the CEO, CSO and CTO of Randamu, respectively Erick Watson, Yolan Romailler, and Patrick McClurg.

-
-
- ) -} \ No newline at end of file +
+ + What is drand and who is behind it? + +

+ The drand project generates random numbers that everyone can trust. It can be applied to create truly random and + verifiable drafts. Initiated in 2017 at EPFL by Nicolas Gailly, drand received support from Philipp Jovanovic + and was guided by Bryan Ford. It has since become independently managed and maintains a network called the{' '} + League of Entropy with EPFL, UCL, Cloudflare, Kudelski + Security, the University of Chile, and Protocol Labs. Current core maintainers of the open source project are + the CEO, CSO and CTO of Randamu, respectively{' '} + Erick Watson,{' '} + Yolan Romailler, and{' '} + Patrick McClurg. +

+
+
+ ); +} diff --git a/website/src/app/[lang]/[region]/(website)/transparency/recipient-selection/page.tsx b/website/src/app/[lang]/[region]/(website)/transparency/recipient-selection/page.tsx index 0a6c4b672..6eaee0219 100644 --- a/website/src/app/[lang]/[region]/(website)/transparency/recipient-selection/page.tsx +++ b/website/src/app/[lang]/[region]/(website)/transparency/recipient-selection/page.tsx @@ -1,58 +1,57 @@ -import {DefaultPageProps} from '@/app/[lang]/[region]'; +import { DefaultPageProps } from '@/app/[lang]/[region]'; import { - DrawCard, - DrawDetail, DrawHeader, - DrawSummary + DrawCard, + DrawDetail, + DrawHeader, + DrawSummary, } from '@/app/[lang]/[region]/(website)/transparency/recipient-selection/drawCard'; -import {loadPastDraws} from '@/app/[lang]/[region]/(website)/transparency/recipient-selection/state'; -import {Translator} from '@socialincome/shared/src/utils/i18n'; -import {BaseContainer, Typography} from '@socialincome/ui'; -import {Explainer} from "@/app/[lang]/[region]/(website)/transparency/recipient-selection/explainer" +import { Explainer } from '@/app/[lang]/[region]/(website)/transparency/recipient-selection/explainer'; +import { loadPastDraws } from '@/app/[lang]/[region]/(website)/transparency/recipient-selection/state'; +import { Translator } from '@socialincome/shared/src/utils/i18n'; +import { BaseContainer, Typography } from '@socialincome/ui'; export default async function Page(props: DefaultPageProps) { - const translator = await Translator.getInstance({language: props.params.lang, namespaces: 'website-selection'}); + const translator = await Translator.getInstance({ language: props.params.lang, namespaces: 'website-selection' }); - const pastDraws = await loadPastDraws().catch(_ => []); - // sort the draws in descending order by time - pastDraws.sort((a, b) => b.time - a.time); + const pastDraws = await loadPastDraws().catch((_) => []); + // sort the draws in descending order by time + pastDraws.sort((a, b) => b.time - a.time); - return ( - - - - {translator.t('past')} - + return ( + + + + {translator.t('past')} + - {pastDraws.length === 0 && {translator.t('none-completed')}} + {pastDraws.length === 0 && {translator.t('none-completed')}} - - - {pastDraws.map((draw) => ( - - } - detail={ - - } - /> - ))} - - ); + + {pastDraws.map((draw) => ( + + } + detail={ + + } + /> + ))} + + ); } - diff --git a/website/src/app/[lang]/[region]/(website)/transparency/recipient-selection/state.ts b/website/src/app/[lang]/[region]/(website)/transparency/recipient-selection/state.ts index 5dade2fae..c67643b39 100644 --- a/website/src/app/[lang]/[region]/(website)/transparency/recipient-selection/state.ts +++ b/website/src/app/[lang]/[region]/(website)/transparency/recipient-selection/state.ts @@ -1,60 +1,60 @@ import * as fs from 'fs/promises'; export type FutureDraw = { - time: number; - name: string; - count: number; - total: number; + time: number; + name: string; + count: number; + total: number; }; export type PastDraw = { - time: number; - name: string; - count: number; - total: number; - drandRound: number; - drandRandomness: string; - filename: string; + time: number; + name: string; + count: number; + total: number; + drandRound: number; + drandRandomness: string; + filename: string; }; type DrawFile = { - time: number; - totalCount: number; - winners: Array; - randomness: string; - round: number; + time: number; + totalCount: number; + winners: Array; + randomness: string; + round: number; }; export async function loadPastDraws(): Promise> { - const drawsPath = '../recipients_selection/draws'; - const files = await fs.readdir(drawsPath); - const draws: Array = []; - - for (const file of files) { - const drawContents = await fs.readFile(`${drawsPath}/${file}`); - const drawFile: DrawFile = JSON.parse(drawContents.toString()); - - draws.push({ - time: drawFile.time, - name: extractDrawName(file), - total: drawFile.totalCount, - drandRound: drawFile.round, - drandRandomness: drawFile.randomness, - count: drawFile.winners.length, - filename: file, - }); - } - - return draws + const drawsPath = '../recipients_selection/draws'; + const files = await fs.readdir(drawsPath); + const draws: Array = []; + + for (const file of files) { + const drawContents = await fs.readFile(`${drawsPath}/${file}`); + const drawFile: DrawFile = JSON.parse(drawContents.toString()); + + draws.push({ + time: drawFile.time, + name: extractDrawName(file), + total: drawFile.totalCount, + drandRound: drawFile.round, + drandRandomness: drawFile.randomness, + count: drawFile.winners.length, + filename: file, + }); + } + + return draws; } // extracts the name from a file of format `{count}-{name}-{date}.txt` and capitalises the first letter function extractDrawName(filename: string): string { - const drawNameMatch = filename.match(/\d-([A-Za-z \-]+)-.*\.txt/); - if (drawNameMatch == null || drawNameMatch.length < 2) { - return ""; - } - const unsanitisedName = drawNameMatch[1]; - const withSpaces = unsanitisedName.replaceAll("-", " "); - return withSpaces.slice(0, 1).toUpperCase() + withSpaces.slice(1).toLowerCase(); + const drawNameMatch = filename.match(/\d-([A-Za-z \-]+)-.*\.txt/); + if (drawNameMatch == null || drawNameMatch.length < 2) { + return ''; + } + const unsanitisedName = drawNameMatch[1]; + const withSpaces = unsanitisedName.replaceAll('-', ' '); + return withSpaces.slice(0, 1).toUpperCase() + withSpaces.slice(1).toLowerCase(); }