From 136c296773fc9307646650219ae3577ede9a00a9 Mon Sep 17 00:00:00 2001 From: Kai Date: Wed, 27 Aug 2025 11:36:08 -0600 Subject: [PATCH 1/4] Update for alignments --- .../plugins/vc-templates/src/templates.ts | 16 +++++++----- packages/plugins/vc-templates/src/types.ts | 4 ++- .../CertificateDisplayCard/AlignmentRow.tsx | 26 ++++++++----------- .../CertificateDisplayCard/AlignmentsBox.tsx | 23 +++++++++------- .../CertificateBackFace.tsx | 4 +-- .../MeritBadgeDisplayCard/AlignmentRow.tsx | 24 +++++++---------- .../MeritBadgeDisplayCard/AlignmentsBox.tsx | 26 +++++++++---------- .../MeritBadgeBackFace.tsx | 4 +-- .../components/VCDisplayCard2/VC2BackFace.tsx | 4 +-- 9 files changed, 66 insertions(+), 65 deletions(-) diff --git a/packages/plugins/vc-templates/src/templates.ts b/packages/plugins/vc-templates/src/templates.ts index 2294d38e26..4ebdf86077 100644 --- a/packages/plugins/vc-templates/src/templates.ts +++ b/packages/plugins/vc-templates/src/templates.ts @@ -118,6 +118,7 @@ export const VC_TEMPLATES: { display, familyTitles, skills, + alignments, groupID = '', } = {}, crypto @@ -146,6 +147,7 @@ export const VC_TEMPLATES: { criteria: { narrative: achievementNarrative, }, + ...(alignments && alignments.length ? { alignments } : {}), }, }, display, @@ -172,6 +174,7 @@ export const VC_TEMPLATES: { address, attachments, skills, + alignments, display, familyTitles, boostID, @@ -204,16 +207,17 @@ export const VC_TEMPLATES: { criteria: { narrative: achievementNarrative, }, + ...(alignments && alignments.length ? { alignments } : {}), }, }, ...(address ? { - address: { - type: ['Address'], - ...address, - ...(address.geo ? { geo: { type: ['GeoCoordinates'], ...address.geo } } : {}), - }, - } + address: { + type: ['Address'], + ...address, + ...(address.geo ? { geo: { type: ['GeoCoordinates'], ...address.geo } } : {}), + }, + } : {}), display, familyTitles, diff --git a/packages/plugins/vc-templates/src/types.ts b/packages/plugins/vc-templates/src/types.ts index 6106b5af04..634e6fad0b 100644 --- a/packages/plugins/vc-templates/src/types.ts +++ b/packages/plugins/vc-templates/src/types.ts @@ -1,4 +1,4 @@ -import { UnsignedVC, VC, UnsignedVP } from '@learncard/types'; +import { UnsignedVC, VC, UnsignedVP, Alignment } from '@learncard/types'; import { DiscriminatedUnionize } from './type.helpers'; import { Plugin } from '@learncard/core'; @@ -90,6 +90,8 @@ export type BoostTemplate = { achievementImage?: string; attachments?: BoostAttachment[]; skills?: BoostSkills[]; + /** OBv3 alignment targets associated with the achievement */ + alignments?: Alignment[]; display?: BoostDisplay; familyTitles?: BoostFamilyTitles; boostID?: BoostID; diff --git a/packages/react-learn-card/src/components/CertificateDisplayCard/AlignmentRow.tsx b/packages/react-learn-card/src/components/CertificateDisplayCard/AlignmentRow.tsx index a40c5eacd3..e6eed8d327 100644 --- a/packages/react-learn-card/src/components/CertificateDisplayCard/AlignmentRow.tsx +++ b/packages/react-learn-card/src/components/CertificateDisplayCard/AlignmentRow.tsx @@ -1,24 +1,20 @@ import React from 'react'; import CaretRightFilled from '../../assets/images/CaretRightFilled.svg'; -type AlignmentsRowProps = { - url?: string - name?: string - framework?: string +type AlignmentRowProps = { + url?: string; + name?: string; + framework?: string; }; -const AlignmentRow:React.FC = ({ - url, - name, - framework - }) => { +const AlignmentRow: React.FC = ({ url, name, framework }) => { return (
-

{framework}

- {/* this might need to change to a link depends on how it comes back after the api call */} - +

{framework}

+ {/* this might need to change to a link depends on how it comes back after the api call */} +
); }; diff --git a/packages/react-learn-card/src/components/CertificateDisplayCard/AlignmentsBox.tsx b/packages/react-learn-card/src/components/CertificateDisplayCard/AlignmentsBox.tsx index a6510e6d5c..18a8dc188a 100644 --- a/packages/react-learn-card/src/components/CertificateDisplayCard/AlignmentsBox.tsx +++ b/packages/react-learn-card/src/components/CertificateDisplayCard/AlignmentsBox.tsx @@ -8,14 +8,14 @@ type Alignment = { targetUrl: string; targetName: string; targetFramework: string; -} +}; type AlignmentsBoxProps = { alignment: Alignment | Alignment[]; - style: 'Certificate' | 'boost'; + style: 'Certificate' | 'boost'; }; -const AlignmentsBox:React.FC = ({ alignment, style }) => { +const AlignmentsBox: React.FC = ({ alignment, style }) => { const [showInfo, setShowInfo] = useState(false); const alignmentText = ` Alignments in your Open Badge credential link your achievement to established frameworks, standards, or competencies. @@ -42,17 +42,20 @@ const AlignmentsBox:React.FC = ({ alignment, style }) => { return (
-

Alignments

+

+ Alignments +

- {showInfo && ( - setShowInfo(false)} - /> - )} + {showInfo && setShowInfo(false)} />} {alignments}
); diff --git a/packages/react-learn-card/src/components/CertificateDisplayCard/CertificateBackFace.tsx b/packages/react-learn-card/src/components/CertificateDisplayCard/CertificateBackFace.tsx index f3e91b147a..680d895148 100644 --- a/packages/react-learn-card/src/components/CertificateDisplayCard/CertificateBackFace.tsx +++ b/packages/react-learn-card/src/components/CertificateDisplayCard/CertificateBackFace.tsx @@ -50,7 +50,7 @@ export const CertificateBackFace: React.FC = ({ const { description } = credentialSubject?.achievement ?? {}; const criteria = credentialSubject?.achievement?.criteria?.narrative; - const alignment = credentialSubject?.achievement?.alignment; + const alignments = credentialSubject?.achievement?.alignments; const credentialDarkColor = getCategoryDarkColor(categoryType); @@ -115,7 +115,7 @@ export const CertificateBackFace: React.FC = ({ /> )} - {alignment && } + {alignments && } {verificationItems && verificationItems.length > 0 && ( diff --git a/packages/react-learn-card/src/components/MeritBadgeDisplayCard/AlignmentRow.tsx b/packages/react-learn-card/src/components/MeritBadgeDisplayCard/AlignmentRow.tsx index a40c5eacd3..e6037b8277 100644 --- a/packages/react-learn-card/src/components/MeritBadgeDisplayCard/AlignmentRow.tsx +++ b/packages/react-learn-card/src/components/MeritBadgeDisplayCard/AlignmentRow.tsx @@ -2,23 +2,19 @@ import React from 'react'; import CaretRightFilled from '../../assets/images/CaretRightFilled.svg'; type AlignmentsRowProps = { - url?: string - name?: string - framework?: string + url?: string; + name?: string; + framework?: string; }; -const AlignmentRow:React.FC = ({ - url, - name, - framework - }) => { +const AlignmentRow: React.FC = ({ url, name, framework }) => { return (
-

{framework}

- {/* this might need to change to a link depends on how it comes back after the api call */} - +

{framework}

+ {/* this might need to change to a link depends on how it comes back after the api call */} +
); }; diff --git a/packages/react-learn-card/src/components/MeritBadgeDisplayCard/AlignmentsBox.tsx b/packages/react-learn-card/src/components/MeritBadgeDisplayCard/AlignmentsBox.tsx index a9a9513261..745043a4c6 100644 --- a/packages/react-learn-card/src/components/MeritBadgeDisplayCard/AlignmentsBox.tsx +++ b/packages/react-learn-card/src/components/MeritBadgeDisplayCard/AlignmentsBox.tsx @@ -24,20 +24,20 @@ const AlignmentsBox: React.FC = ({ alignment, style }) => { const alignments = Array.isArray(alignment) ? alignment.map((object, index) => ( - - )) + + )) : alignment && ( - - ); + + ); return (
diff --git a/packages/react-learn-card/src/components/MeritBadgeDisplayCard/MeritBadgeBackFace.tsx b/packages/react-learn-card/src/components/MeritBadgeDisplayCard/MeritBadgeBackFace.tsx index 84e35f96ab..ce7556261a 100644 --- a/packages/react-learn-card/src/components/MeritBadgeDisplayCard/MeritBadgeBackFace.tsx +++ b/packages/react-learn-card/src/components/MeritBadgeDisplayCard/MeritBadgeBackFace.tsx @@ -50,7 +50,7 @@ export const MeritBadgeBackFace: React.FC = ({ const { description } = credentialSubject?.achievement ?? {}; const criteria = credentialSubject?.achievement?.criteria?.narrative; - const alignment = credentialSubject?.achievement?.alignment; + const alignments = credentialSubject?.achievement?.alignments; const credentialDarkColor = getCategoryDarkColor(categoryType); @@ -115,7 +115,7 @@ export const MeritBadgeBackFace: React.FC = ({ /> )} - {alignment && } + {alignments && } {verificationItems && verificationItems.length > 0 && ( diff --git a/packages/react-learn-card/src/components/VCDisplayCard2/VC2BackFace.tsx b/packages/react-learn-card/src/components/VCDisplayCard2/VC2BackFace.tsx index 5156a964b8..86b329d024 100644 --- a/packages/react-learn-card/src/components/VCDisplayCard2/VC2BackFace.tsx +++ b/packages/react-learn-card/src/components/VCDisplayCard2/VC2BackFace.tsx @@ -75,7 +75,7 @@ const VC2BackFace: React.FC = ({ : undefined; const criteria = achievement?.criteria?.narrative; const description = achievement?.description; - const alignment = achievement?.alignment; + const alignments = achievement?.alignments; /* const tags = credential.credentialSubject.achievement?.tag; @@ -164,7 +164,7 @@ const VC2BackFace: React.FC = ({ )} {/* {credential.notes && } */} - {alignment && } + {alignments && } {verificationItems && verificationItems.length > 0 && ( From aca861eed2b79969733658cc93850d5243fafdde Mon Sep 17 00:00:00 2001 From: Kai Date: Thu, 28 Aug 2025 14:04:09 -0600 Subject: [PATCH 2/4] add storybook tests --- .../CertificateDisplayCard.stories.tsx | 16 ++++++++++++++- .../src/helpers/test.helpers.ts | 20 +++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/packages/react-learn-card/src/components/CertificateDisplayCard/CertificateDisplayCard.stories.tsx b/packages/react-learn-card/src/components/CertificateDisplayCard/CertificateDisplayCard.stories.tsx index 98a6aea4e8..2a6d65149e 100644 --- a/packages/react-learn-card/src/components/CertificateDisplayCard/CertificateDisplayCard.stories.tsx +++ b/packages/react-learn-card/src/components/CertificateDisplayCard/CertificateDisplayCard.stories.tsx @@ -1,6 +1,8 @@ import React from 'react'; import { Story, Meta } from '@storybook/react'; import CertificateDisplayCard from './CertificateDisplayCard'; +import { AllFieldsCredential } from '../../helpers/test.helpers'; +import { TestVerificationItems } from '../../helpers/test.helpers'; export default { title: 'CertificateDisplayCard', @@ -12,4 +14,16 @@ export default { const Template: Story = args => ; export const CertificateDisplayCardTest = Template.bind({}); -CertificateDisplayCardTest.args = {}; +CertificateDisplayCardTest.args = { + credential: AllFieldsCredential, + verificationItems: [ + TestVerificationItems.SUCCESS.PROOF, + TestVerificationItems.SUCCESS.NO_EXPIRATION, + TestVerificationItems.SUCCESS.NOT_EXPIRED, + TestVerificationItems.FAILED.EXPIRED, + TestVerificationItems.FAILED.CONTEXT, + TestVerificationItems.FAILED.SIGNATURE, + TestVerificationItems.FAILED.PROOF_TYPE, + TestVerificationItems.FAILED.APPLICABLE_PROOF, + ], +}; diff --git a/packages/react-learn-card/src/helpers/test.helpers.ts b/packages/react-learn-card/src/helpers/test.helpers.ts index 9e591e5f0a..18d176d83e 100644 --- a/packages/react-learn-card/src/helpers/test.helpers.ts +++ b/packages/react-learn-card/src/helpers/test.helpers.ts @@ -103,6 +103,26 @@ export const AllFieldsCredential = { narrative: 'You really know your crednetials! Pretty rad. Being able to find and handle all of these fields is incredible! This narrative is going to be pretty long so that we can see if you can handle long blocks of text too. This should probably be truncated off by now or something.', }, + alignments: [ + { + type: 'Alignment', + targetType: 'Competency', + targetName: 'Teamwork', + targetDescription: 'Teamwork is the ability to work well with others', + targetFramework: '21st Century Skills', + targetUrl: 'https://example.com/alignments/21st-century-skills/teamwork', + targetCode: '123', + }, + { + type: ['Alignment'], + targetType: 'Competency', + targetName: 'Quick Thinker', + targetDescription: 'Quick Thinker is the ability to think quickly', + targetFramework: '21st Century Skills', + targetUrl: 'https://example.com/alignments/21st-century-skills/quick-thinker', + targetCode: '456', + }, + ], }, }, proof: { From f3e39f69e4de8cd5151c54566ffdaf9baad7723e Mon Sep 17 00:00:00 2001 From: Kai Date: Thu, 28 Aug 2025 14:05:21 -0600 Subject: [PATCH 3/4] changeset --- .changeset/upset-mugs-appear.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/upset-mugs-appear.md diff --git a/.changeset/upset-mugs-appear.md b/.changeset/upset-mugs-appear.md new file mode 100644 index 0000000000..edacfd5c33 --- /dev/null +++ b/.changeset/upset-mugs-appear.md @@ -0,0 +1,6 @@ +--- +'@learncard/vc-templates-plugin': patch +'@learncard/react': patch +--- + +Properly handle Obv3 alignments field From ef94948d8b3772ba67c250c9cfe4e6b2b310d7da Mon Sep 17 00:00:00 2001 From: Kai Date: Wed, 17 Sep 2025 11:56:20 -0600 Subject: [PATCH 4/4] fix react svg --- .../src/components/svgs/ThreeDotVertical.tsx | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/react-learn-card/src/components/svgs/ThreeDotVertical.tsx b/packages/react-learn-card/src/components/svgs/ThreeDotVertical.tsx index 30fb517ca1..b0db5c77dd 100644 --- a/packages/react-learn-card/src/components/svgs/ThreeDotVertical.tsx +++ b/packages/react-learn-card/src/components/svgs/ThreeDotVertical.tsx @@ -14,25 +14,25 @@ export const ThreeDotVertical = ({ className }: { className?: string }) => { d="M8.4375 10C8.4375 10.8629 9.13706 11.5625 10 11.5625C10.8629 11.5625 11.5625 10.8629 11.5625 10C11.5625 9.13706 10.8629 8.4375 10 8.4375C9.13706 8.4375 8.4375 9.13705 8.4375 10Z" fill="#6F7590" stroke="#6F7590" - stroke-width="2" - stroke-linecap="round" - stroke-linejoin="round" + strokeWidth="2" + strokeLinecap="round" + strokeLinejoin="round" /> );