Skip to content

Commit 36861c7

Browse files
authored
Merge pull request #2633 from objectcomputing/feature-2615/CertBadgeCorrections
Feature 2615/cert badge corrections
2 parents 52a42d6 + 4d2e245 commit 36861c7

File tree

7 files changed

+200
-3
lines changed

7 files changed

+200
-3
lines changed

web-ui/src/api/certification.js

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import { resolve } from './api.js';
2+
3+
const certificationUrl = '/services/certification';
4+
5+
export const getCertifications = async cookie => {
6+
return resolve({
7+
url: certificationUrl,
8+
headers: { 'X-CSRF-Header': cookie, Accept: 'application/json' }
9+
});
10+
};
11+
12+
export const getCertification = async (id, cookie) => {
13+
return resolve({
14+
url: `${certificationUrl}/${id}`,
15+
headers: { 'X-CSRF-Header': cookie, Accept: 'application/json' }
16+
});
17+
};
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
.earned-certification-badges {
2+
img {
3+
max-height: 5rem;
4+
}
5+
6+
.MuiCardContent-root {
7+
display: flex;
8+
gap: 1rem;
9+
}
10+
}
Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
import PropTypes from 'prop-types';
2+
import React, { useCallback, useContext, useEffect, useState } from 'react';
3+
4+
import {Card, CardContent, CardHeader, Chip, Tooltip} from '@mui/material';
5+
6+
import { resolve } from '../../api/api.js';
7+
import { AppContext } from '../../context/AppContext';
8+
import { selectCsrfToken } from '../../context/selectors';
9+
10+
import './EarnedCertificationBadges.css';
11+
import certifications from "../certifications/Certifications.jsx";
12+
13+
const earnedCertificationBaseUrl = '/services/earned-certification';
14+
15+
const propTypes = {
16+
memberId: PropTypes.string, certifications: PropTypes.array,
17+
};
18+
const EarnedCertificationBadges = ({ memberId, certifications }) => {
19+
const [earnedCertifications, setEarnedCertifications] = useState([]);
20+
21+
const { state } = useContext(AppContext);
22+
const csrf = selectCsrfToken(state);
23+
24+
const loadCertifications = useCallback(async () => {
25+
const res = await resolve({
26+
method: 'GET',
27+
url: earnedCertificationBaseUrl + '?memberId=' + memberId,
28+
headers: {
29+
'X-CSRF-Header': csrf,
30+
Accept: 'application/json',
31+
'Content-Type': 'application/json;charset=UTF-8'
32+
}
33+
});
34+
if (res.error) return;
35+
36+
const certifications = res.payload.data;
37+
setEarnedCertifications(certifications);
38+
}, [csrf]);
39+
40+
useEffect(() => {
41+
if (csrf) loadCertifications();
42+
}, [csrf]);
43+
44+
if (earnedCertifications.length === 0 || !certifications) return null;
45+
return (
46+
<Card className="earned-certification-badges">
47+
<CardHeader
48+
title="Earned Certifications"
49+
titleTypographyProps={{ variant: 'h5', component: 'h1' }}
50+
/>
51+
<CardContent>
52+
{earnedCertifications.map(earnedCert => {
53+
// Find the corresponding certification using earnedCert.certificationId
54+
const cert = certifications.find(c => c.id === earnedCert.certificationId);
55+
// If no matching cert is found, skip rendering for that earnedCert
56+
if (!cert) return null;
57+
if (cert.badgeUrl && cert.badgeUrl.trim().length > 0) {
58+
return (
59+
<Tooltip
60+
key={earnedCert.id}
61+
title={
62+
<>
63+
{cert.name} <br />
64+
Issued on: {earnedCert.earnedDate} <br />
65+
Expires on: {earnedCert.expirationDate}
66+
</>
67+
}
68+
>
69+
{earnedCert.validationUrl ? (
70+
<a href={earnedCert.validationUrl} target="_blank" rel="noopener noreferrer">
71+
<img
72+
alt={`${cert.name}, Issued on: ${earnedCert.earnedDate}, Expires on: ${earnedCert.expirationDate}`}
73+
src={cert.badgeUrl}
74+
/>
75+
</a>
76+
) : (
77+
<img
78+
alt={`${cert.name}, Issued on: ${earnedCert.earnedDate}, Expires on: ${earnedCert.expirationDate}`}
79+
src={cert.badgeUrl}
80+
/>
81+
)}
82+
</Tooltip>
83+
);
84+
} else {
85+
return (
86+
<>
87+
{earnedCert.validationUrl ? (
88+
<a href={earnedCert.validationUrl} target="_blank" rel="noopener noreferrer" style={{ textDecoration: 'none' }}>
89+
<Chip
90+
sx={{
91+
height: 'auto',
92+
'& .MuiChip-label': {
93+
display: 'block',
94+
whiteSpace: 'normal',
95+
},
96+
}}
97+
className="chip"
98+
color="primary"
99+
key={earnedCert.id}
100+
label={
101+
<>
102+
{cert.name} <br />
103+
Issued on: {earnedCert.earnedDate}<br />
104+
Expires on: {earnedCert.expirationDate}
105+
</>
106+
}
107+
/>
108+
</a>
109+
) : (
110+
<Chip
111+
sx={{
112+
height: 'auto',
113+
'& .MuiChip-label': {
114+
display: 'block',
115+
whiteSpace: 'normal',
116+
},
117+
}}
118+
className="chip"
119+
color="primary"
120+
key={earnedCert.id}
121+
label={
122+
<>
123+
{cert.name} <br />
124+
Issued on: {earnedCert.earnedDate}<br />
125+
Expires on: {earnedCert.expirationDate}
126+
</>
127+
}
128+
/>
129+
)}
130+
</>
131+
);
132+
}
133+
})}
134+
</CardContent>
135+
</Card>
136+
);
137+
138+
};
139+
140+
EarnedCertificationBadges.propTypes = propTypes;
141+
142+
export default EarnedCertificationBadges;

web-ui/src/context/AppContext.jsx

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
UPDATE_MEMBER_PROFILES,
1212
UPDATE_TERMINATED_MEMBERS,
1313
UPDATE_SKILLS,
14+
UPDATE_CERTIFICATIONS,
1415
UPDATE_TEAMS,
1516
UPDATE_PEOPLE_LOADING,
1617
UPDATE_TEAMS_LOADING
@@ -26,6 +27,7 @@ import { BASE_API_URL } from '../api/api';
2627
import { getAllGuilds } from '../api/guild';
2728
import { getSkills } from '../api/skill';
2829
import { getAllTeams } from '../api/team';
30+
import {getCertifications} from "../api/certification.js";
2931

3032
const AppContext = React.createContext();
3133

@@ -51,6 +53,7 @@ const AppContextProvider = props => {
5153
memberProfiles,
5254
checkins,
5355
skills,
56+
certifications,
5457
roles,
5558
userRoles
5659
} = state;
@@ -214,6 +217,26 @@ const AppContextProvider = props => {
214217
}
215218
}, [csrf, skills]);
216219

220+
useEffect(() => {
221+
const getAllCertifications = async () => {
222+
const res = await getCertifications(csrf);
223+
const data =
224+
res &&
225+
res.payload &&
226+
res.payload.data &&
227+
res.payload.status === 200 &&
228+
!res.error
229+
? res.payload.data
230+
: null;
231+
if (data && data.length > 0) {
232+
dispatch({ type: UPDATE_CERTIFICATIONS, payload: data });
233+
}
234+
};
235+
if (csrf && !certifications) {
236+
getAllCertifications();
237+
}
238+
}, [csrf, certifications]);
239+
217240
useEffect(() => {
218241
const getRoles = async () => {
219242
const res = await getAllRoles(csrf);

web-ui/src/context/actions.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export const UPDATE_MEMBER_SKILLS = '@@check-ins/update_member_skills';
2222
export const ADD_ROLE = '@@check-ins/add_role';
2323
export const UPDATE_SKILL = '@@check-ins/update_skill';
2424
export const UPDATE_SKILLS = '@@check-ins/update_skills';
25+
export const UPDATE_CERTIFICATIONS = '@@check-ins/update_certifications';
2526
export const UPDATE_TEAM_MEMBERS = '@@check-ins/update_team_members';
2627
export const UPDATE_TEAMS = '@@check-ins/update_teams';
2728
export const UPDATE_TERMINATED_MEMBERS =

web-ui/src/context/reducer.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
UPDATE_MEMBER_SKILLS,
2121
UPDATE_SKILL,
2222
UPDATE_SKILLS,
23+
UPDATE_CERTIFICATIONS,
2324
UPDATE_GUILD,
2425
UPDATE_GUILDS,
2526
ADD_ROLE,
@@ -113,6 +114,9 @@ export const reducer = (state, action) => {
113114
case UPDATE_SKILLS:
114115
state.skills = action.payload;
115116
break;
117+
case UPDATE_CERTIFICATIONS:
118+
state.certifications = action.payload;
119+
break;
116120
case SET_CSRF:
117121
state.csrf = action.payload;
118122
break;

web-ui/src/pages/MemberProfilePage.jsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,11 @@ import { getTeamByMember } from '../api/team';
1616
import { getGuildsForMember } from '../api/guild';
1717
import { getAvatarURL } from '../api/api.js';
1818
import ProfilePage from './ProfilePage';
19-
import CertificationBadges from '../components/certifications/CertificationBadges';
2019
import VolunteerBadges from '../components/volunteer/VolunteerBadges';
2120
import { levelList } from '../context/util';
2221
import StarIcon from '@mui/icons-material/Star';
2322
import KudosDialog from '../components/kudos_dialog/KudosDialog';
23+
import EarnedCertificationBadges from "../components/earned-certifications/EarnedCertificationBadges.jsx";
2424

2525
import {
2626
Avatar,
@@ -40,7 +40,7 @@ import './MemberProfilePage.css';
4040
const MemberProfilePage = () => {
4141
const { state } = useContext(AppContext);
4242
const history = useHistory();
43-
const { csrf, skills, userProfile } = state;
43+
const { csrf, skills, certifications, userProfile } = state;
4444
const { memberId } = useParams();
4545
const [selectedMember, setSelectedMember] = useState(null);
4646
const [kudosDialogOpen, setKudosDialogOpen] = useState(false);
@@ -330,7 +330,7 @@ const MemberProfilePage = () => {
330330
</div>
331331
</CardContent>
332332
</Card>
333-
<CertificationBadges memberId={memberId} />
333+
<EarnedCertificationBadges memberId={memberId} certifications={certifications} />
334334
<VolunteerBadges memberId={memberId} />
335335
</Grid>
336336
</Grid>

0 commit comments

Comments
 (0)