Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Credit Class Page "Additional Info" improvements #1980

Merged
merged 10 commits into from
Jul 26, 2023
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')) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

currently just "Duration" inclusion checking this but ultimately once this: regen-network/regen-registry-standards#57 (comment) is fixed, we should check for schema:Duration equality

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.
Comment on lines +30 to +31
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we create a followup task blocked by regen-network/regen-registry-standards#57?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done: #1983

'@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.
Comment on lines +39 to +41
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

'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 };