diff --git a/web-registry/src/components/molecules/MetaDetail/MetaDetail.BaseValue.tsx b/web-registry/src/components/molecules/MetaDetail/MetaDetail.BaseValue.tsx index e63f687351..9d250060f4 100644 --- a/web-registry/src/components/molecules/MetaDetail/MetaDetail.BaseValue.tsx +++ b/web-registry/src/components/molecules/MetaDetail/MetaDetail.BaseValue.tsx @@ -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'; @@ -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; }; -const MetaDetailBaseValue: React.FC = ({ value, rdfType, bodySize }) => { - if (!value) return null; - +const MetaDetailBaseValue: React.FC = ({ + 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 && {formattedValue}} - {isCompactedNameUrlOrOptionalUrl(value) && ( - + {formattedValue && ( + + {formattedValue} + + )} + {value && isCompactedNameUrlOrOptionalUrl(value) && ( + = ({ value={v} rdfType={rdfType} bodySize={bodySize} + sx={{ pb: value.length > 1 ? 2 : 0 }} /> )) ) : ( diff --git a/web-registry/src/components/molecules/MetaDetail/MetaDetail.utils.ts b/web-registry/src/components/molecules/MetaDetail/MetaDetail.utils.ts new file mode 100644 index 0000000000..76ffa0a49e --- /dev/null +++ b/web-registry/src/components/molecules/MetaDetail/MetaDetail.utils.ts @@ -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)}`; +} diff --git a/web-registry/src/lib/db/types/json-ld/credit-class-metadata.ts b/web-registry/src/lib/db/types/json-ld/credit-class-metadata.ts index 41d8842e32..c4864a01e1 100644 --- a/web-registry/src/lib/db/types/json-ld/credit-class-metadata.ts +++ b/web-registry/src/lib/db/types/json-ld/credit-class-metadata.ts @@ -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; +}; diff --git a/web-registry/src/lib/rdf/rdf.unknown-fields.ts b/web-registry/src/lib/rdf/rdf.unknown-fields.ts index 8dda28c010..533e896c3f 100644 --- a/web-registry/src/lib/rdf/rdf.unknown-fields.ts +++ b/web-registry/src/lib/rdf/rdf.unknown-fields.ts @@ -29,6 +29,9 @@ const knownClassFields: KnownClassFields[] = [ 'regen:carbonOffsetStandard', 'regen:tokenizationSource', 'regen:certifications', + 'regen:coBenefits', + 'regen:measuredGHGs', + 'regen:bufferPoolAccounts', ]; export function getClassUnknownFields( diff --git a/web-registry/src/pages/CreditClassDetails/CreditClassDetails.AdditionalInfo.tsx b/web-registry/src/pages/CreditClassDetails/CreditClassDetails.AdditionalInfo.tsx index 50d2459cdc..20f7ecd6ea 100644 --- a/web-registry/src/pages/CreditClassDetails/CreditClassDetails.AdditionalInfo.tsx +++ b/web-registry/src/pages/CreditClassDetails/CreditClassDetails.AdditionalInfo.tsx @@ -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 { + metadata?: T; creditTypeName?: string; } -const AdditionalInfo: React.FC> = - ({ metadata, creditTypeName }) => { - if (!metadata) return null; +const AdditionalInfo = ({ + metadata, + creditTypeName, +}: AdditionalInfoProps): 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 ( - - + return ( + + + + + + + + + + + + + + + {unknownFields.map(([fieldName, value]) => ( - - - - - - - - - - {unknownFields.map(([fieldName, value]) => ( - - ))} - - - ); - }; + ))} + + + ); +}; export { AdditionalInfo }; diff --git a/web-registry/src/pages/CreditClassDetails/CreditClassDetails.ApprovedMethodologies.tsx b/web-registry/src/pages/CreditClassDetails/CreditClassDetails.ApprovedMethodologies.tsx index c31de68025..8264751338 100644 --- a/web-registry/src/pages/CreditClassDetails/CreditClassDetails.ApprovedMethodologies.tsx +++ b/web-registry/src/pages/CreditClassDetails/CreditClassDetails.ApprovedMethodologies.tsx @@ -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'; @@ -28,27 +27,16 @@ const ApprovedMethodologiesList: React.FC< {methodologies.slice(0, MAX_METHODOLOGIE_LINKS).map(methodologie => { return ( - - - {methodologie?.['schema:name']} - - - + + ); })} {count > MAX_METHODOLOGIE_LINKS && methodologyList?.['schema:url'] && ( diff --git a/web-registry/src/pages/CreditClassDetails/CreditClassDetails.BufferPoolAccounts.tsx b/web-registry/src/pages/CreditClassDetails/CreditClassDetails.BufferPoolAccounts.tsx new file mode 100644 index 0000000000..ca1811ce7d --- /dev/null +++ b/web-registry/src/pages/CreditClassDetails/CreditClassDetails.BufferPoolAccounts.tsx @@ -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 = ({ bufferPoolAccounts }) => { + if (!bufferPoolAccounts) return null; + + const count = bufferPoolAccounts?.length; + + if (!count || count < 1) return null; + + return ( + + {bufferPoolAccounts.map(account => ( + + + + ))} + + } + /> + ); +}; + +export { BufferPoolAccounts };