Skip to content

Commit

Permalink
feat: Credit Class Page "Additional Info" improvements (#1980)
Browse files Browse the repository at this point in the history
* feat: add custom label for measuredGHGs and coBenefits

* feat: add support for boolean unknown fields

* refactor: avoid null return

* feat: add support for ISO8601 formatted durations

* fix: duration value formatting

* fix: rm offset gen method from additional info since already displayed above

* fix: add spacing between items if metadata value is a list

* feat: add support for bufferPoolAccounts

* feat: add BufferPoolAccounts component

* refactor: use LinkWithArrow in BufferPoolAccounts and ApprovedMethodologiesList
  • Loading branch information
blushi authored Jul 26, 2023
1 parent 1d1eddd commit 26ccc7c
Show file tree
Hide file tree
Showing 8 changed files with 172 additions and 76 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { SxProps, Theme } from '@mui/material';
import { ExpandedTermDefinition } from 'jsonld';
import { sxToArray } from 'utils/mui/sxToArray';

import { Body } from 'web-components/lib/components/typography';
import { TextSize } from 'web-components/lib/components/typography/sizing';
Expand All @@ -7,34 +9,49 @@ import { formatDate, formatNumber } from 'web-components/lib/utils/format';
import { LinkWithArrow } from 'components/atoms';

import { BaseValue, isCompactedNameUrlOrOptionalUrl } from './MetaDetail.types';
import { fromISO8601 } from './MetaDetail.utils';

export type Props = {
value?: BaseValue;
rdfType?: ExpandedTermDefinition['@type'];
bodySize?: TextSize;
sx?: SxProps<Theme>;
};

const MetaDetailBaseValue: React.FC<Props> = ({ value, rdfType, bodySize }) => {
if (!value) return null;

const MetaDetailBaseValue: React.FC<Props> = ({
value,
rdfType,
bodySize,
sx,
}) => {
let formattedValue: string | undefined;
const isNumber = typeof value === 'number';
const isString = typeof value === 'string';
const isBoolean = typeof value === 'boolean';
if (isString || isNumber) {
if (isNumber) {
formattedValue = formatNumber({ num: value });
} else if (rdfType === 'xsd:date') {
formattedValue = formatDate(value);
} else if (rdfType?.includes('Duration')) {
formattedValue = fromISO8601(value) || value;
} else {
formattedValue = value;
}
} else if (isBoolean) {
const toString = (value as boolean).toString();
formattedValue = toString.charAt(0).toUpperCase() + toString.slice(1);
}

return (
<>
{formattedValue && <Body size={bodySize}>{formattedValue}</Body>}
{isCompactedNameUrlOrOptionalUrl(value) && (
<Body size={bodySize} styleLinks={false}>
{formattedValue && (
<Body size={bodySize} sx={sxToArray(sx)}>
{formattedValue}
</Body>
)}
{value && isCompactedNameUrlOrOptionalUrl(value) && (
<Body size={bodySize} styleLinks={false} sx={sxToArray(sx)}>
<LinkWithArrow
href={value['schema:url']}
label={value['schema:name']}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const MetaDetail: React.FC<Props> = ({
value={v}
rdfType={rdfType}
bodySize={bodySize}
sx={{ pb: value.length > 1 ? 2 : 0 }}
/>
))
) : (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { formatDuration } from 'date-fns';

export function fromISO8601(iso8601Duration: string) {
const iso8601DurationRegex =
/(-)?P(-)?(?:([.,\d]+)Y)?(?:([.,\d]+)M)?(?:([.,\d]+)W)?(?:([.,\d]+)D)?(?:T(?:([.,\d]+)H)?(?:([.,\d]+)M)?(?:([.,\d]+)S)?)?/;
var matches = iso8601Duration.match(iso8601DurationRegex);
const duration = {
years: matches?.[3] ? Number(matches?.[3]) : 0,
months: matches?.[4] ? Number(matches?.[4]) : 0,
weeks: matches?.[5] ? Number(matches?.[5]) : 0,
days: matches?.[6] ? Number(matches?.[6]) : 0,
hours: matches?.[7] ? Number(matches?.[7]) : 0,
minutes: matches?.[8] ? Number(matches?.[8]) : 0,
seconds: matches?.[9] ? Number(matches?.[9]) : 0,
};
if (Object.values(duration).findIndex(v => v !== 0) > -1)
return `${
matches?.[1] === '-' || matches?.[2] === '-' ? 'Previous ' : ''
}${formatDuration(duration)}`;
}
18 changes: 18 additions & 0 deletions web-registry/src/lib/db/types/json-ld/credit-class-metadata.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,22 @@ export interface CreditClassMetadataLD {
'regen:carbonOffsetStandard'?: CompactedNameUrl;
'regen:tokenizationSource'?: string;
'regen:certifications'?: Certification[];
'regen:coBenefits'?: string[];
'regen:measuredGHGs': string[];
'regen:bufferPoolAccounts': {
// We probably want to simplify this to be just a @list instead,
// but keeping as is, until C04 class metadata is updated accordingly.
'@type': 'schema:ItemList';
'schema:itemListElement': BufferPoolAccount[];
};
}

export type BufferPoolAccount = {
'schema:name': string;
// Keeping both regen:walletAddress and regen:address for now,
// until C04 class metadata gets fixed.
// But ultimately, we should just use regen:address.
'regen:walletAddress'?: string;
'regen:address'?: string;
'regen:poolAllocation': string;
};
3 changes: 3 additions & 0 deletions web-registry/src/lib/rdf/rdf.unknown-fields.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,9 @@ const knownClassFields: KnownClassFields[] = [
'regen:carbonOffsetStandard',
'regen:tokenizationSource',
'regen:certifications',
'regen:coBenefits',
'regen:measuredGHGs',
'regen:bufferPoolAccounts',
];

export function getClassUnknownFields<T extends CreditClassMetadataLD>(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,62 +11,68 @@ import {
import { MetaDetail } from 'components/molecules';

import { ApprovedMethodologiesList } from './CreditClassDetails.ApprovedMethodologies';
import { BufferPoolAccounts } from './CreditClassDetails.BufferPoolAccounts';

interface AdditionalInfoProps {
metadata?: CreditClassMetadataLD;
interface AdditionalInfoProps<T extends CreditClassMetadataLD> {
metadata?: T;
creditTypeName?: string;
}

const AdditionalInfo: React.FC<React.PropsWithChildren<AdditionalInfoProps>> =
({ metadata, creditTypeName }) => {
if (!metadata) return null;
const AdditionalInfo = <T extends CreditClassMetadataLD>({
metadata,
creditTypeName,
}: AdditionalInfoProps<T>): JSX.Element | null => {
if (!metadata) return null;

const offsetGenerationMethods = metadata?.['regen:offsetGenerationMethod'];
const sectoralScopes = metadata?.['regen:sectoralScope'];
const verificationMethod = metadata?.['regen:verificationMethod'];
const sourceRegistry = metadata?.['regen:sourceRegistry'];
const ecosystemTypes = metadata?.['regen:ecosystemType'];
const projectActivities = metadata?.['regen:projectActivities'];
const carbonOffsetStandard = metadata?.['regen:carbonOffsetStandard'];
const tokenizationSource = metadata?.['regen:tokenizationSource'];
const sectoralScopes = metadata?.['regen:sectoralScope'];
const verificationMethod = metadata?.['regen:verificationMethod'];
const sourceRegistry = metadata?.['regen:sourceRegistry'];
const ecosystemTypes = metadata?.['regen:ecosystemType'];
const projectActivities = metadata?.['regen:projectActivities'];
const carbonOffsetStandard = metadata?.['regen:carbonOffsetStandard'];
const tokenizationSource = metadata?.['regen:tokenizationSource'];
const coBenefits = metadata?.['regen:coBenefits'];
const measuredGHGs = metadata?.['regen:measuredGHGs'];
const bufferPoolAccounts = metadata?.['regen:bufferPoolAccounts'];

const unknownFields = getClassUnknownFields(metadata);
const unknownFields = getClassUnknownFields(metadata);

return (
<Box sx={{ py: 8 }}>
<Grid container spacing={8}>
return (
<Box sx={{ py: 8 }}>
<Grid container spacing={8}>
<MetaDetail
label="credit type"
value={capitalizeWord(creditTypeName)}
/>
<MetaDetail label="registry" value={sourceRegistry} />
<MetaDetail
label="carbon offset standard"
value={carbonOffsetStandard}
/>
<ApprovedMethodologiesList
methodologyList={metadata['regen:approvedMethodologies']}
/>
<MetaDetail label="project activities" value={projectActivities} />
<MetaDetail label="sectoral scopes" value={sectoralScopes} />
<MetaDetail label="Tokenization Source" value={tokenizationSource} />
<MetaDetail label="ecosystem type" value={ecosystemTypes} />
<MetaDetail label="verification method" value={verificationMethod} />
<MetaDetail label="co-benefits" value={coBenefits} />
<MetaDetail label="measured GHGs" value={measuredGHGs} />
<BufferPoolAccounts
bufferPoolAccounts={bufferPoolAccounts?.['schema:itemListElement']}
/>
{unknownFields.map(([fieldName, value]) => (
<MetaDetail
label="credit type"
value={capitalizeWord(creditTypeName)}
key={fieldName}
label={getFieldLabel(fieldName)}
value={value}
rdfType={getFieldType(fieldName, metadata['@context'])}
/>
<MetaDetail label="registry" value={sourceRegistry} />
<MetaDetail
label="carbon offset standard"
value={carbonOffsetStandard}
/>
<ApprovedMethodologiesList
methodologyList={metadata['regen:approvedMethodologies']}
/>
<MetaDetail
label="offset generation methods"
value={offsetGenerationMethods}
/>
<MetaDetail label="project activities" value={projectActivities} />
<MetaDetail label="sectoral scopes" value={sectoralScopes} />
<MetaDetail label="Tokenization Source" value={tokenizationSource} />
<MetaDetail label="ecosystem type" value={ecosystemTypes} />
<MetaDetail label="verification method" value={verificationMethod} />
{unknownFields.map(([fieldName, value]) => (
<MetaDetail
key={fieldName}
label={getFieldLabel(fieldName)}
value={value}
rdfType={getFieldType(fieldName, metadata['@context'])}
/>
))}
</Grid>
</Box>
);
};
))}
</Grid>
</Box>
);
};

export { AdditionalInfo };
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import { Flex } from 'web-components/lib/components/box';
import SmallArrowIcon from 'web-components/lib/components/icons/SmallArrowIcon';
import { Body, Label } from 'web-components/lib/components/typography';

import { ApprovedMethodologies } from 'lib/db/types/json-ld/methodology';

import { Link } from 'components/atoms';
import { Link, LinkWithArrow } from 'components/atoms';
import { MetaDetail } from 'components/molecules';

import { MAX_METHODOLOGIE_LINKS } from './CreditClassDetails.constants';
Expand All @@ -28,27 +27,16 @@ const ApprovedMethodologiesList: React.FC<
<Flex flexDirection="column">
{methodologies.slice(0, MAX_METHODOLOGIE_LINKS).map(methodologie => {
return (
<Link
sx={{
display: 'flex',
color: 'secondary.main',
}}
href={methodologie?.['schema:url']}
target="_blank"
<Body
key={methodologie?.['schema:name']}
size="xl"
styleLinks={false}
>
<Body size="xl" key={methodologie?.['schema:name']}>
{methodologie?.['schema:name']}
<SmallArrowIcon
sx={{
mb: 0.3,
height: 9,
width: 13,
ml: 2,
display: 'inline',
}}
/>
</Body>
</Link>
<LinkWithArrow
href={methodologie?.['schema:url']}
label={methodologie?.['schema:name']}
/>
</Body>
);
})}
{count > MAX_METHODOLOGIE_LINKS && methodologyList?.['schema:url'] && (
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Flex } from 'web-components/lib/components/box';
import { Body } from 'web-components/lib/components/typography';

import { getAccountUrl } from 'lib/block-explorer';
import { BufferPoolAccount } from 'lib/db/types/json-ld/credit-class-metadata';

import { LinkWithArrow } from 'components/atoms';
import { MetaDetail } from 'components/molecules';

type Props = {
bufferPoolAccounts?: BufferPoolAccount[];
};

const BufferPoolAccounts: React.FC<Props> = ({ bufferPoolAccounts }) => {
if (!bufferPoolAccounts) return null;

const count = bufferPoolAccounts?.length;

if (!count || count < 1) return null;

return (
<MetaDetail
label="buffer pool accounts"
customContent={
<Flex flexDirection="column">
{bufferPoolAccounts.map(account => (
<Body key={account?.['schema:name']} size="xl" styleLinks={false}>
<LinkWithArrow
href={getAccountUrl(
account?.['regen:walletAddress'] ||
account?.['regen:address'],
)}
label={account?.['schema:name']}
/>
</Body>
))}
</Flex>
}
/>
);
};

export { BufferPoolAccounts };

0 comments on commit 26ccc7c

Please sign in to comment.