diff --git a/api-server/src/server/boot/challenge.js b/api-server/src/server/boot/challenge.js index 2ae10c0b95a..d109a744709 100644 --- a/api-server/src/server/boot/challenge.js +++ b/api-server/src/server/boot/challenge.js @@ -719,14 +719,42 @@ function createMsTrophyChallengeCompleted(app) { } const { msTrophyId = '' } = challenge; - const msTrophyApiUrl = `https://learn.microsoft.com/api/gamestatus/achievements/${msTrophyId}?username=${msUsername}&locale=en-us`; - const msApiRes = await fetch(msTrophyApiUrl); - const msTrophyJson = await msApiRes.json(); - if (!msApiRes.ok || msTrophyJson.awardType !== 'Trophy') { + const msProfileApi = `https://learn.microsoft.com/api/profiles/${msUsername}`; + const msProfileApiRes = await fetch(msProfileApi); + const msProfileJson = await msProfileApiRes.json(); + + if (!msProfileApiRes.ok || !msProfileJson.userId) { return res.status(403).json({ type: 'error', - message: 'flash.ms.trophy.err-3', + message: 'flash.ms.profile.err', + variables: { + msUsername + } + }); + } + + const { userId } = msProfileJson; + + const msGameStatusApi = `https://learn.microsoft.com/api/gamestatus/${userId}`; + const msGameStatusApiRes = await fetch(msGameStatusApi); + const msGameStatusJson = await msGameStatusApiRes.json(); + + if (!msGameStatusApiRes.ok) { + return res.status(403).json({ + type: 'error', + message: 'flash.ms.trophy.err-3' + }); + } + + const hasEarnedTrophy = msGameStatusJson.achievements?.some( + a => a.awardUid === msTrophyId + ); + + if (!hasEarnedTrophy) { + return res.status(403).json({ + type: 'error', + message: 'flash.ms.trophy.err-4', variables: { msUsername } @@ -735,7 +763,7 @@ function createMsTrophyChallengeCompleted(app) { const completedChallenge = pick(body, ['id']); - completedChallenge.solution = msTrophyApiUrl; + completedChallenge.solution = msGameStatusApi; completedChallenge.completedDate = Date.now(); try { @@ -765,7 +793,7 @@ function createMsTrophyChallengeCompleted(app) { log(e); return res.status(500).json({ type: 'error', - message: 'flash.ms.trophy.err-4' + message: 'flash.ms.trophy.err-5' }); } }; diff --git a/client/i18n/locales/english/translations.json b/client/i18n/locales/english/translations.json index f8fd1298ec2..9326392715c 100644 --- a/client/i18n/locales/english/translations.json +++ b/client/i18n/locales/english/translations.json @@ -732,11 +732,15 @@ "unlinked": "The link to your Microsoft username has been removed.", "unlink-err": "Something went wrong trying to remove the link to your Microsoft username." }, + "profile": { + "err": "We could not find a Microsoft user ID for Microsoft user \"{{msUsername}}\"" + }, "trophy": { "err-1": "We could not find a Microsoft username associated with your freeCodeCamp account.", "err-2": "You are trying to submit a challenge that does not appear to be a trophy challenge.", - "err-3": "It appears that the Microsoft user \"{{msUsername}}\" has not earned this trophy.", - "err-4": "Something went wrong trying to verify your trophy. Please check and try again.", + "err-3": "We could not get your Microsoft profile from your Microsoft ID.", + "err-4": "It appears that the Microsoft user \"{{msUsername}}\" has not earned this trophy.", + "err-5": "Something went wrong trying to verify your trophy. Please check and try again.", "verified": "Your trophy from Microsoft's learning platform was verified." } }, diff --git a/client/src/components/Flash/index.tsx b/client/src/components/Flash/index.tsx index c288837dcae..e31039849ac 100644 --- a/client/src/components/Flash/index.tsx +++ b/client/src/components/Flash/index.tsx @@ -16,7 +16,8 @@ function Flash({ flashMessage, removeFlashMessage }: FlashProps): JSX.Element { const { type, message, id, variables } = flashMessage; const { t } = useTranslation(); - const flashStyle = type as AlertProps['variant']; + const flashStyle = + type === 'error' ? 'danger' : (type as AlertProps['variant']); function handleClose() { removeFlashMessage(); diff --git a/client/src/components/Flash/redux/flash-messages.ts b/client/src/components/Flash/redux/flash-messages.ts index f69781f2b3c..d03c73ea51d 100644 --- a/client/src/components/Flash/redux/flash-messages.ts +++ b/client/src/components/Flash/redux/flash-messages.ts @@ -27,10 +27,12 @@ export enum FlashMessages { MsTranscriptLinked = 'flash.ms.transcript.linked', MsTranscriptUnlinked = 'flash.ms.transcript.unlinked', MsTranscriptUnlinkErr = 'flash.ms.transcript.unlink-err', + MsProfileErr = 'flash.ms.profile.err', MsTrophyErr1 = 'flash.ms.trophy.err-1', MsTrophyErr2 = 'flash.ms.trophy.err-2', MsTrophyErr3 = 'flash.ms.trophy.err-3', MsTrophyErr4 = 'flash.ms.trophy.err-4', + MsTrophyErr5 = 'flash.ms.trophy.err-5', MsTrophyVerified = 'flash.ms.trophy.verified', NameNeeded = 'flash.name-needed', None = '', diff --git a/client/src/utils/tone/index.ts b/client/src/utils/tone/index.ts index 9f056869e63..beb50d1e9d3 100644 --- a/client/src/utils/tone/index.ts +++ b/client/src/utils/tone/index.ts @@ -42,10 +42,12 @@ const toneUrls = { [FlashMessages.MsTranscriptLinked]: CHAL_COMP, [FlashMessages.MsTranscriptUnlinked]: CHAL_COMP, [FlashMessages.MsTranscriptUnlinkErr]: TRY_AGAIN, + [FlashMessages.MsProfileErr]: TRY_AGAIN, [FlashMessages.MsTrophyErr1]: TRY_AGAIN, [FlashMessages.MsTrophyErr2]: TRY_AGAIN, [FlashMessages.MsTrophyErr3]: TRY_AGAIN, [FlashMessages.MsTrophyErr4]: TRY_AGAIN, + [FlashMessages.MsTrophyErr5]: TRY_AGAIN, [FlashMessages.MsTrophyVerified]: CHAL_COMP, [FlashMessages.NameNeeded]: TRY_AGAIN, // [FlashMessages.None]: '',