From 44f5cd6cfcdde55b9e605a9096315a035e377890 Mon Sep 17 00:00:00 2001 From: Oliver Eyton-Williams Date: Fri, 12 Dec 2025 14:51:05 +0100 Subject: [PATCH] feat: add user flags for all certifications (#64338) Co-authored-by: Shaun Hamilton --- api/prisma/schema.prisma | 7 ++ api/src/plugins/__fixtures__/user.ts | 7 ++ api/src/routes/helpers/certificate-utils.ts | 4 +- api/src/routes/helpers/user-utils.ts | 7 ++ api/src/routes/protected/certificate.test.ts | 69 ++++++++----------- api/src/routes/protected/certificate.ts | 63 ++++++++--------- api/src/routes/protected/user.test.ts | 14 ++++ api/src/routes/protected/user.ts | 7 ++ api/src/routes/public/certificate.ts | 7 ++ api/src/schemas/types.ts | 10 ++- api/src/schemas/user/get-session-user.ts | 7 ++ api/src/utils/create-user.ts | 7 ++ .../src/client-only-routes/show-settings.tsx | 15 +++- .../settings/certification.test.tsx | 7 ++ .../src/components/settings/certification.tsx | 8 +-- client/src/redux/prop-types.ts | 34 ++------- .../components/super-block-map.tsx | 7 +- .../a1-professional-chinese.yml | 2 +- .../a2-professional-chinese.yml | 2 +- .../a2-professional-spanish.yml | 2 +- .../b1-english-for-developers.yml | 2 +- shared/config/certification-settings.test.ts | 2 +- shared/config/certification-settings.ts | 59 ++++++++++++++-- 23 files changed, 219 insertions(+), 130 deletions(-) diff --git a/api/prisma/schema.prisma b/api/prisma/schema.prisma index a350a99c8cc..3bffec134a3 100644 --- a/api/prisma/schema.prisma +++ b/api/prisma/schema.prisma @@ -134,6 +134,13 @@ model user { is2018DataVisCert Boolean? // Undefined is2018FullStackCert Boolean? // Undefined isCollegeAlgebraPyCertV8 Boolean? // Undefined + isFrontEndLibsCertV9 Boolean? // Undefined + isBackEndDevApisCertV9 Boolean? // Undefined + isFullStackDeveloperCertV9 Boolean? // Undefined + isB1EnglishCert Boolean? // Undefined + isA2SpanishCert Boolean? // Undefined + isA2ChineseCert Boolean? // Undefined + isA1ChineseCert Boolean? // Undefined // isUpcomingPythonCertV8 Boolean? // Undefined. It is in the db but has never been used. keyboardShortcuts Boolean? // Undefined linkedin String? // Null | Undefined diff --git a/api/src/plugins/__fixtures__/user.ts b/api/src/plugins/__fixtures__/user.ts index 22cd4d08452..b9ac8e07be8 100644 --- a/api/src/plugins/__fixtures__/user.ts +++ b/api/src/plugins/__fixtures__/user.ts @@ -56,6 +56,13 @@ export const newUser = (email: string) => ({ isRespWebDesignCert: false, isRespWebDesignCertV9: false, isSciCompPyCertV7: false, + isFrontEndLibsCertV9: false, + isBackEndDevApisCertV9: false, + isFullStackDeveloperCertV9: false, + isB1EnglishCert: false, + isA2SpanishCert: false, + isA2ChineseCert: false, + isA1ChineseCert: false, keyboardShortcuts: false, linkedin: null, location: '', diff --git a/api/src/routes/helpers/certificate-utils.ts b/api/src/routes/helpers/certificate-utils.ts index d33cec87714..ab26370b066 100644 --- a/api/src/routes/helpers/certificate-utils.ts +++ b/api/src/routes/helpers/certificate-utils.ts @@ -21,9 +21,7 @@ const fullStackCertificateIds = [ * @param certSlug - The certification slug to check. * @returns True if the certification slug is known, otherwise false. */ -export function isKnownCertSlug( - certSlug: string -): certSlug is keyof typeof certSlugTypeMap { +export function isKnownCertSlug(certSlug: string): certSlug is Certification { return certSlug in certSlugTypeMap; } diff --git a/api/src/routes/helpers/user-utils.ts b/api/src/routes/helpers/user-utils.ts index b1b76992a46..c8babbc5726 100644 --- a/api/src/routes/helpers/user-utils.ts +++ b/api/src/routes/helpers/user-utils.ts @@ -34,6 +34,13 @@ const nullableFlags = [ 'isRespWebDesignCertV9', 'isSciCompPyCertV7', 'isDataAnalysisPyCertV7', + 'isFrontEndLibsCertV9', + 'isBackEndDevApisCertV9', + 'isFullStackDeveloperCertV9', + 'isB1EnglishCert', + 'isA2SpanishCert', + 'isA2ChineseCert', + 'isA1ChineseCert', // isUpcomingPythonCertV8 exists in the db, but is not returned by the api-server // TODO(Post-MVP): delete it from the db? 'keyboardShortcuts' diff --git a/api/src/routes/protected/certificate.test.ts b/api/src/routes/protected/certificate.test.ts index 1f293c0649e..d3ef3ed54cc 100644 --- a/api/src/routes/protected/certificate.test.ts +++ b/api/src/routes/protected/certificate.test.ts @@ -13,6 +13,7 @@ import { defaultUserEmail, defaultUserId, devLogin, + resetDefaultUser, setupServer, superRequest } from '../../../vitest.utils.js'; @@ -35,30 +36,13 @@ describe('certificate routes', () => { describe('PUT /certificate/verify', () => { beforeEach(async () => { + await resetDefaultUser(); await fastifyTestInstance.prisma.user.updateMany({ where: { email: defaultUserEmail }, data: { - completedChallenges: [], - is2018DataVisCert: false, - isA2EnglishCert: false, - isApisMicroservicesCert: false, - isCollegeAlgebraPyCertV8: false, - isDataAnalysisPyCertV7: false, - isFoundationalCSharpCertV8: false, - isFrontEndLibsCert: false, - isInfosecCertV7: false, - isJsAlgoDataStructCert: false, - isJavascriptCertV9: false, - isMachineLearningPyCertV7: false, - isPythonCertV9: false, - isQaCertV7: false, - isRelationalDatabaseCertV8: false, - isRelationalDatabaseCertV9: false, - isRespWebDesignCert: false, - isRespWebDesignCertV9: false, - isSciCompPyCertV7: false, name: 'fcc', - username: 'fcc' + username: 'fcc', + completedChallenges: [] } }); }); @@ -174,7 +158,6 @@ describe('certificate routes', () => { await fastifyTestInstance.prisma.user.updateMany({ where: { email: defaultUserEmail }, data: { - completedChallenges: [], isRespWebDesignCert: true } }); @@ -309,29 +292,37 @@ describe('certificate routes', () => { } }, isCertMap: { + is2018DataVisCert: false, + isA1ChineseCert: false, + isA2ChineseCert: false, isA2EnglishCert: false, - isRespWebDesignCert: true, - isRespWebDesignCertV9: false, + isA2SpanishCert: false, + isApisMicroservicesCert: false, + isB1EnglishCert: false, + isBackEndCert: false, + isBackEndDevApisCertV9: false, + isCollegeAlgebraPyCertV8: false, + isDataAnalysisPyCertV7: false, + isDataVisCert: false, + isFoundationalCSharpCertV8: false, + isFrontEndCert: false, + isFrontEndLibsCert: false, + isFrontEndLibsCertV9: false, + isFullStackCert: false, + isFullStackDeveloperCertV9: false, + isInfosecCertV7: false, + isInfosecQaCert: false, isJavascriptCertV9: false, isJsAlgoDataStructCert: false, - isFrontEndLibsCert: false, - is2018DataVisCert: false, - isApisMicroservicesCert: false, - isInfosecQaCert: false, - isQaCertV7: false, - isInfosecCertV7: false, - isFrontEndCert: false, - isBackEndCert: false, - isDataVisCert: false, - isFullStackCert: false, - isSciCompPyCertV7: false, - isDataAnalysisPyCertV7: false, + isJsAlgoDataStructCertV8: false, isMachineLearningPyCertV7: false, - isRelationalDatabaseCertV8: false, - isCollegeAlgebraPyCertV8: false, - isFoundationalCSharpCertV8: false, isPythonCertV9: false, - isRelationalDatabaseCertV9: false + isQaCertV7: false, + isRelationalDatabaseCertV8: false, + isRelationalDatabaseCertV9: false, + isRespWebDesignCert: true, + isRespWebDesignCertV9: false, + isSciCompPyCertV7: false }, completedChallenges: [ { diff --git a/api/src/routes/protected/certificate.ts b/api/src/routes/protected/certificate.ts index a382278a064..18b019504d9 100644 --- a/api/src/routes/protected/certificate.ts +++ b/api/src/routes/protected/certificate.ts @@ -5,6 +5,7 @@ import type { FastifyPluginCallbackTypebox } from '@fastify/type-provider-typebo import { challenges, getChallenges } from '../../utils/get-challenges.js'; import { Certification, + type CertificationFlags, certSlugTypeMap, certToIdMap, certToTitleMap, @@ -121,45 +122,26 @@ export function createCertLookup( return certLookup; } -interface CertI { - isA2EnglishCert?: boolean; - isApisMicroservicesCert?: boolean; - isBackEndCert?: boolean; - isCollegeAlgebraPyCertV8?: boolean; - isDataAnalysisPyCertV7?: boolean; - isDataVisCert?: boolean; - isFrontEndCert?: boolean; - isFrontEndLibsCert?: boolean; - isFoundationalCSharpCertV8?: boolean; - isFullStackCert?: boolean; - isInfosecCertV7?: boolean; - isInfosecQaCert?: boolean; - isJavascriptCertV9?: boolean; - isJsAlgoDataStructCert?: boolean; - isJsAlgoDataStructCertV8?: boolean; - isMachineLearningPyCertV7?: boolean; - isPythonCertV9?: boolean; - isQaCertV7?: boolean; - isRelationalDatabaseCertV8?: boolean; - isRelationalDatabaseCertV9?: boolean; - isRespWebDesignCert?: boolean; - isRespWebDesignCertV9?: boolean; - isSciCompPyCertV7?: boolean; - is2018DataVisCert?: boolean; -} - -function getUserIsCertMap(user: CertI) { +function getUserIsCertMap(user: Partial) { const { + is2018DataVisCert = false, + isA1ChineseCert = false, + isA2ChineseCert = false, isA2EnglishCert = false, + isA2SpanishCert = false, isApisMicroservicesCert = false, + isB1EnglishCert = false, isBackEndCert = false, + isBackEndDevApisCertV9 = false, isCollegeAlgebraPyCertV8 = false, isDataAnalysisPyCertV7 = false, isDataVisCert = false, + isFoundationalCSharpCertV8 = false, isFrontEndCert = false, isFrontEndLibsCert = false, - isFoundationalCSharpCertV8 = false, + isFrontEndLibsCertV9 = false, isFullStackCert = false, + isFullStackDeveloperCertV9 = false, isInfosecCertV7 = false, isInfosecQaCert = false, isJavascriptCertV9 = false, @@ -172,21 +154,28 @@ function getUserIsCertMap(user: CertI) { isRelationalDatabaseCertV9 = false, isRespWebDesignCert = false, isRespWebDesignCertV9 = false, - isSciCompPyCertV7 = false, - is2018DataVisCert = false + isSciCompPyCertV7 = false } = user; return { + is2018DataVisCert, + isA1ChineseCert, + isA2ChineseCert, isA2EnglishCert, + isA2SpanishCert, isApisMicroservicesCert, + isB1EnglishCert, isBackEndCert, + isBackEndDevApisCertV9, isCollegeAlgebraPyCertV8, isDataAnalysisPyCertV7, isDataVisCert, + isFoundationalCSharpCertV8, isFrontEndCert, isFrontEndLibsCert, - isFoundationalCSharpCertV8, + isFrontEndLibsCertV9, isFullStackCert, + isFullStackDeveloperCertV9, isInfosecCertV7, isInfosecQaCert, isJavascriptCertV9, @@ -199,8 +188,7 @@ function getUserIsCertMap(user: CertI) { isRelationalDatabaseCertV9, isRespWebDesignCert, isRespWebDesignCertV9, - isSciCompPyCertV7, - is2018DataVisCert + isSciCompPyCertV7 }; } @@ -346,16 +334,23 @@ export const protectedCertificateRoutes: FastifyPluginCallbackTypebox = ( username: true, is2018DataVisCert: true, is2018FullStackCert: true, + isA1ChineseCert: true, + isA2ChineseCert: true, isA2EnglishCert: true, + isA2SpanishCert: true, isApisMicroservicesCert: true, + isB1EnglishCert: true, isBackEndCert: true, + isBackEndDevApisCertV9: true, isCollegeAlgebraPyCertV8: true, isDataAnalysisPyCertV7: true, isDataVisCert: true, isFoundationalCSharpCertV8: true, isFrontEndCert: true, isFrontEndLibsCert: true, + isFrontEndLibsCertV9: true, isFullStackCert: true, + isFullStackDeveloperCertV9: true, isInfosecCertV7: true, isInfosecQaCert: true, isJavascriptCertV9: true, diff --git a/api/src/routes/protected/user.test.ts b/api/src/routes/protected/user.test.ts index 1eb56c8d68a..ade695aa1e3 100644 --- a/api/src/routes/protected/user.test.ts +++ b/api/src/routes/protected/user.test.ts @@ -300,6 +300,13 @@ const publicUserData = { isRespWebDesignCert: testUserData.isRespWebDesignCert, isRespWebDesignCertV9: testUserData.isRespWebDesignCertV9, isSciCompPyCertV7: testUserData.isSciCompPyCertV7, + isFrontEndLibsCertV9: testUserData.isFrontEndLibsCertV9, + isBackEndDevApisCertV9: testUserData.isBackEndDevApisCertV9, + isFullStackDeveloperCertV9: testUserData.isFullStackDeveloperCertV9, + isB1EnglishCert: testUserData.isB1EnglishCert, + isA2SpanishCert: testUserData.isA2SpanishCert, + isA2ChineseCert: testUserData.isA2ChineseCert, + isA1ChineseCert: testUserData.isA1ChineseCert, linkedin: testUserData.linkedin, location: testUserData.location, name: testUserData.name, @@ -1029,6 +1036,13 @@ describe('userRoutes', () => { isRespWebDesignCert: false, isRespWebDesignCertV9: false, isSciCompPyCertV7: false, + isFrontEndLibsCertV9: false, + isBackEndDevApisCertV9: false, + isFullStackDeveloperCertV9: false, + isB1EnglishCert: false, + isA2SpanishCert: false, + isA2ChineseCert: false, + isA1ChineseCert: false, keyboardShortcuts: false, location: '', name: '', diff --git a/api/src/routes/protected/user.ts b/api/src/routes/protected/user.ts index 03dd79a7113..4c8d6bc2e48 100644 --- a/api/src/routes/protected/user.ts +++ b/api/src/routes/protected/user.ts @@ -708,6 +708,13 @@ export const userGetRoutes: FastifyPluginCallbackTypebox = ( isRespWebDesignCert: true, isRespWebDesignCertV9: true, isSciCompPyCertV7: true, + isFrontEndLibsCertV9: true, + isBackEndDevApisCertV9: true, + isFullStackDeveloperCertV9: true, + isB1EnglishCert: true, + isA2SpanishCert: true, + isA2ChineseCert: true, + isA1ChineseCert: true, keyboardShortcuts: true, linkedin: true, location: true, diff --git a/api/src/routes/public/certificate.ts b/api/src/routes/public/certificate.ts index b2cb2187d53..33aa8aced37 100644 --- a/api/src/routes/public/certificate.ts +++ b/api/src/routes/public/certificate.ts @@ -84,6 +84,13 @@ export const unprotectedCertificateRoutes: FastifyPluginCallbackTypebox = ( isRelationalDatabaseCertV9: true, isCollegeAlgebraPyCertV8: true, isFoundationalCSharpCertV8: true, + isFrontEndLibsCertV9: true, + isBackEndDevApisCertV9: true, + isFullStackDeveloperCertV9: true, + isB1EnglishCert: true, + isA2SpanishCert: true, + isA2ChineseCert: true, + isA1ChineseCert: true, isHonest: true, username: true, name: true, diff --git a/api/src/schemas/types.ts b/api/src/schemas/types.ts index 3ef99115c7b..48b4af03cb4 100644 --- a/api/src/schemas/types.ts +++ b/api/src/schemas/types.ts @@ -28,7 +28,15 @@ export const isCertMap = Type.Object({ isRelationalDatabaseCertV8: Type.Boolean(), isRelationalDatabaseCertV9: Type.Boolean(), isCollegeAlgebraPyCertV8: Type.Boolean(), - isFoundationalCSharpCertV8: Type.Boolean() + isFoundationalCSharpCertV8: Type.Boolean(), + isJsAlgoDataStructCertV8: Type.Boolean(), + isA1ChineseCert: Type.Boolean(), + isA2ChineseCert: Type.Boolean(), + isA2SpanishCert: Type.Boolean(), + isB1EnglishCert: Type.Boolean(), + isBackEndDevApisCertV9: Type.Boolean(), + isFullStackDeveloperCertV9: Type.Boolean(), + isFrontEndLibsCertV9: Type.Boolean() }); export const file = Type.Object({ diff --git a/api/src/schemas/user/get-session-user.ts b/api/src/schemas/user/get-session-user.ts index 72265d5283c..87fd49fcda5 100644 --- a/api/src/schemas/user/get-session-user.ts +++ b/api/src/schemas/user/get-session-user.ts @@ -93,6 +93,13 @@ export const getSessionUser = { isRespWebDesignCert: Type.Boolean(), isRespWebDesignCertV9: Type.Boolean(), isSciCompPyCertV7: Type.Boolean(), + isFrontEndLibsCertV9: Type.Boolean(), + isBackEndDevApisCertV9: Type.Boolean(), + isFullStackDeveloperCertV9: Type.Boolean(), + isB1EnglishCert: Type.Boolean(), + isA2SpanishCert: Type.Boolean(), + isA2ChineseCert: Type.Boolean(), + isA1ChineseCert: Type.Boolean(), keyboardShortcuts: Type.Boolean(), linkedin: Type.Optional(Type.String()), location: Type.String(), diff --git a/api/src/utils/create-user.ts b/api/src/utils/create-user.ts index 87be5838a57..f3cda706ed2 100644 --- a/api/src/utils/create-user.ts +++ b/api/src/utils/create-user.ts @@ -39,6 +39,13 @@ export const createResetProperties = () => ({ isRespWebDesignCert: false, isRespWebDesignCertV9: false, isSciCompPyCertV7: false, + isFrontEndLibsCertV9: false, + isBackEndDevApisCertV9: false, + isFullStackDeveloperCertV9: false, + isB1EnglishCert: false, + isA2SpanishCert: false, + isA2ChineseCert: false, + isA1ChineseCert: false, needsModeration: false, partiallyCompletedChallenges: [], // TODO(Post-MVP): Omit this from the document? (prisma will always return []) progressTimestamps: [Date.now()], // TODO(Post-MVP): This may need normalising before we can omit it. Also, does it need to start with a timestamp? diff --git a/client/src/client-only-routes/show-settings.tsx b/client/src/client-only-routes/show-settings.tsx index 3f9595e8182..ed18d19ea89 100644 --- a/client/src/client-only-routes/show-settings.tsx +++ b/client/src/client-only-routes/show-settings.tsx @@ -163,6 +163,13 @@ export function ShowSettings(props: ShowSettingsProps): JSX.Element { isCollegeAlgebraPyCertV8, isFoundationalCSharpCertV8, isJsAlgoDataStructCertV8, + isFrontEndLibsCertV9, + isBackEndDevApisCertV9, + isFullStackDeveloperCertV9, + isB1EnglishCert, + isA2SpanishCert, + isA2ChineseCert, + isA1ChineseCert, isEmailVerified, isHonest, sendQuincyEmail, @@ -251,9 +258,15 @@ export function ShowSettings(props: ShowSettingsProps): JSX.Element { isRespWebDesignCertV9={isRespWebDesignCertV9} isSciCompPyCertV7={isSciCompPyCertV7} isJsAlgoDataStructCertV8={isJsAlgoDataStructCertV8} + isFrontEndLibsCertV9={isFrontEndLibsCertV9} + isBackEndDevApisCertV9={isBackEndDevApisCertV9} + isFullStackDeveloperCertV9={isFullStackDeveloperCertV9} + isB1EnglishCert={isB1EnglishCert} + isA2SpanishCert={isA2SpanishCert} + isA2ChineseCert={isA2ChineseCert} + isA1ChineseCert={isA1ChineseCert} username={username} verifyCert={verifyCert} - isEmailVerified={isEmailVerified} /> diff --git a/client/src/components/settings/certification.test.tsx b/client/src/components/settings/certification.test.tsx index a2e50bbc38c..938767a2e60 100644 --- a/client/src/components/settings/certification.test.tsx +++ b/client/src/components/settings/certification.test.tsx @@ -286,6 +286,13 @@ const defaultTestProps = { isRelationalDatabaseCertV9: false, isCollegeAlgebraPyCertV8: false, isFoundationalCSharpCertV8: false, + isFrontEndLibsCertV9: false, + isBackEndDevApisCertV9: false, + isFullStackDeveloperCertV9: false, + isB1EnglishCert: false, + isA2SpanishCert: false, + isA2ChineseCert: false, + isA1ChineseCert: false, username: 'developmentuser', verifyCert: verifyCert, isEmailVerified: false diff --git a/client/src/components/settings/certification.tsx b/client/src/components/settings/certification.tsx index 013ddc7297c..333a2178f68 100644 --- a/client/src/components/settings/certification.tsx +++ b/client/src/components/settings/certification.tsx @@ -19,13 +19,13 @@ import { Certification, currentCertifications, legacyCertifications, - upcomingCertifications + upcomingCertifications, + type CertificationFlags } from '../../../../shared-dist/config/certification-settings'; import env from '../../../config/env.json'; import type { ChallengeData, - ClaimedCertifications, CompletedChallenge, GeneratedExamResults, User @@ -66,7 +66,7 @@ const createCertifiedMap = ({ isCollegeAlgebraPyCertV8, isFoundationalCSharpCertV8, isJsAlgoDataStructCertV8 -}: ClaimedCertifications): Record< +}: CertificationFlags): Record< Exclude, boolean > => ({ @@ -115,7 +115,7 @@ type CertificationSettingsProps = { t: TFunction; verifyCert: typeof verifyCert; openModal: typeof openModal; -} & ClaimedCertifications & +} & CertificationFlags & Pick; const LegacyFullStack = (props: CertificationSettingsProps) => { diff --git a/client/src/redux/prop-types.ts b/client/src/redux/prop-types.ts index 89ddfb0a3e2..dc452cded8c 100644 --- a/client/src/redux/prop-types.ts +++ b/client/src/redux/prop-types.ts @@ -1,8 +1,9 @@ import { HandlerProps } from 'react-reflex'; -import { +import type { ChallengeLang, SuperBlocks } from '../../../shared-dist/config/curriculum'; +import type { CertificationFlags } from '../../../shared-dist/config/certification-settings'; import type { Chapter } from '../../../shared-dist/config/chapters'; import { BlockLayouts, BlockLabel } from '../../../shared-dist/config/blocks'; import type { ChallengeFile, Ext } from '../../../shared-dist/utils/polyvinyl'; @@ -424,6 +425,7 @@ export type User = { email: string; emailVerified: boolean; githubProfile: string; + isEmailVerified: boolean; isBanned: boolean; isCheater: boolean; isDonating: boolean; @@ -447,7 +449,7 @@ export type User = { username: string; website: string; yearsTopContributor: string[]; -} & ClaimedCertifications; +} & CertificationFlags; export type ProfileUI = { isLocked: boolean; @@ -462,34 +464,6 @@ export type ProfileUI = { showTimeLine: boolean; }; -export type ClaimedCertifications = { - is2018DataVisCert: boolean; - isA2EnglishCert: boolean; - isApisMicroservicesCert: boolean; - isBackEndCert: boolean; - isDataVisCert: boolean; - isEmailVerified: boolean; - isCollegeAlgebraPyCertV8: boolean; - isFoundationalCSharpCertV8: boolean; - isFrontEndCert: boolean; - isFrontEndLibsCert: boolean; - isFullStackCert: boolean; - isInfosecQaCert: boolean; - isJavascriptCertV9: boolean; - isPythonCertV9: boolean; - isQaCertV7: boolean; - isInfosecCertV7: boolean; - isJsAlgoDataStructCert: boolean; - isRelationalDatabaseCertV8: boolean; - isRelationalDatabaseCertV9: boolean; - isRespWebDesignCert: boolean; - isRespWebDesignCertV9: boolean; - isSciCompPyCertV7: boolean; - isDataAnalysisPyCertV7: boolean; - isMachineLearningPyCertV7: boolean; - isJsAlgoDataStructCertV8: boolean; -}; - type SavedChallenges = SavedChallenge[]; export type SavedChallenge = { diff --git a/client/src/templates/Introduction/components/super-block-map.tsx b/client/src/templates/Introduction/components/super-block-map.tsx index 3859a4683fe..9355a975d68 100644 --- a/client/src/templates/Introduction/components/super-block-map.tsx +++ b/client/src/templates/Introduction/components/super-block-map.tsx @@ -10,7 +10,6 @@ import { import type { CertTitle } from '../../../../config/cert-and-project-map'; import type { ChapterBasedSuperBlockStructure, - ClaimedCertifications, User } from '../../../redux/prop-types'; import type { @@ -146,11 +145,7 @@ export const SuperBlockMap = ({ const requirementLink = `/learn/${requirement}/`; const certSlug = superBlockToCertMap[requirement]; - const certFlagLookup = certSlugTypeMap as Record< - string, - keyof ClaimedCertifications - >; - const certFlagKey = certSlug ? certFlagLookup[certSlug] : undefined; + const certFlagKey = certSlug ? certSlugTypeMap[certSlug] : undefined; const isRequirementComplete = Boolean( certFlagKey && user?.[certFlagKey] ); diff --git a/curriculum/challenges/english/certifications/a1-professional-chinese.yml b/curriculum/challenges/english/certifications/a1-professional-chinese.yml index 18e2cfcb69d..5c5368e668f 100644 --- a/curriculum/challenges/english/certifications/a1-professional-chinese.yml +++ b/curriculum/challenges/english/certifications/a1-professional-chinese.yml @@ -1,7 +1,7 @@ --- id: 68f1268149f045a650d4229e title: A1 Professional Chinese Certification -certification: a1-professional-chinese-v8 +certification: a1-professional-chinese challengeType: 7 tests: - id: 688f1daf0133dbe2a36b140b diff --git a/curriculum/challenges/english/certifications/a2-professional-chinese.yml b/curriculum/challenges/english/certifications/a2-professional-chinese.yml index 6c99a86a971..add6488444b 100644 --- a/curriculum/challenges/english/certifications/a2-professional-chinese.yml +++ b/curriculum/challenges/english/certifications/a2-professional-chinese.yml @@ -1,7 +1,7 @@ --- id: 682c3153086dd7cabe7f48bc title: A2 Professional Chinese Certification -certification: a2-professional-chinese-v8 +certification: a2-professional-chinese challengeType: 7 tests: - id: 682c2753317b88f1ecdad894 diff --git a/curriculum/challenges/english/certifications/a2-professional-spanish.yml b/curriculum/challenges/english/certifications/a2-professional-spanish.yml index 60d88b7baaf..57ce2dd10c8 100644 --- a/curriculum/challenges/english/certifications/a2-professional-spanish.yml +++ b/curriculum/challenges/english/certifications/a2-professional-spanish.yml @@ -1,7 +1,7 @@ --- id: 681a6b22e5a782fe3459984a title: A2 Professional Spanish Certification -certification: a2-professional-spanish-v8 +certification: a2-professional-spanish challengeType: 7 tests: - id: 681a8796e5a782fe3459984b diff --git a/curriculum/challenges/english/certifications/b1-english-for-developers.yml b/curriculum/challenges/english/certifications/b1-english-for-developers.yml index ac8ae334788..721dc3489f6 100644 --- a/curriculum/challenges/english/certifications/b1-english-for-developers.yml +++ b/curriculum/challenges/english/certifications/b1-english-for-developers.yml @@ -1,7 +1,7 @@ --- id: 66607e53317411dd5e8aae21 title: B1 English for Developers Certification -certification: b1-english-for-developers-v8 +certification: b1-english-for-developers challengeType: 7 tests: - id: 66607e5b317411dd5e8aae22 diff --git a/shared/config/certification-settings.test.ts b/shared/config/certification-settings.test.ts index 5ca4d582c6a..98ca17cdbbf 100644 --- a/shared/config/certification-settings.test.ts +++ b/shared/config/certification-settings.test.ts @@ -35,7 +35,7 @@ describe('certToIdMap', () => { }); describe('certSlugTypeMap', () => { - it.todo('should contain a value for all certifications', () => { + it('should contain a value for all certifications', () => { const allCertifications = Object.values(Certification).sort(); const certSlugTypeMapKeys = Object.keys(certSlugTypeMap).sort(); diff --git a/shared/config/certification-settings.ts b/shared/config/certification-settings.ts index 093cea1f59e..eb95a91ff24 100644 --- a/shared/config/certification-settings.ts +++ b/shared/config/certification-settings.ts @@ -34,10 +34,10 @@ export enum Certification { BackEndDevApisV9 = 'back-end-development-and-apis-v9', A2English = 'a2-english-for-developers', FullStackDeveloperV9 = 'full-stack-developer-v9', - B1English = 'b1-english-for-developers-v8', - A2Spanish = 'a2-professional-spanish-v8', - A2Chinese = 'a2-professional-chinese-v8', - A1Chinese = 'a1-professional-chinese-v8', + B1English = 'b1-english-for-developers', + A2Spanish = 'a2-professional-spanish', + A2Chinese = 'a2-professional-chinese', + A1Chinese = 'a1-professional-chinese', // Legacy certifications LegacyFrontEnd = 'legacy-front-end', JsAlgoDataStruct = 'javascript-algorithms-and-data-structures', @@ -175,7 +175,40 @@ export const completionHours: Record = { [Certification.A1Chinese]: 300 }; -export const certSlugTypeMap = { +type UserCertFlag = + | 'isFrontEndCert' + | 'isJsAlgoDataStructCert' + | 'isBackEndCert' + | 'isDataVisCert' + | 'isInfosecQaCert' + | 'isFullStackCert' + | 'isRespWebDesignCert' + | 'isJsAlgoDataStructCertV8' + | 'isFrontEndLibsCert' + | 'is2018DataVisCert' + | 'isApisMicroservicesCert' + | 'isQaCertV7' + | 'isInfosecCertV7' + | 'isSciCompPyCertV7' + | 'isDataAnalysisPyCertV7' + | 'isMachineLearningPyCertV7' + | 'isRelationalDatabaseCertV8' + | 'isCollegeAlgebraPyCertV8' + | 'isFoundationalCSharpCertV8' + | 'isA2EnglishCert' + | 'isRespWebDesignCertV9' + | 'isJavascriptCertV9' + | 'isFrontEndLibsCertV9' + | 'isPythonCertV9' + | 'isRelationalDatabaseCertV9' + | 'isBackEndDevApisCertV9' + | 'isFullStackDeveloperCertV9' + | 'isB1EnglishCert' + | 'isA2SpanishCert' + | 'isA2ChineseCert' + | 'isA1ChineseCert'; + +export const certSlugTypeMap: Record = { // legacy [Certification.LegacyFrontEnd]: 'isFrontEndCert', [Certification.JsAlgoDataStruct]: 'isJsAlgoDataStructCert', @@ -202,9 +235,21 @@ export const certSlugTypeMap = { [Certification.PythonV9]: 'isPythonCertV9', [Certification.RelationalDbV9]: 'isRelationalDatabaseCertV9', [Certification.RespWebDesignV9]: 'isRespWebDesignCertV9', - [Certification.JsV9]: 'isJavascriptCertV9' + [Certification.JsV9]: 'isJavascriptCertV9', + // upcoming -} as const; + [Certification.FrontEndDevLibsV9]: 'isFrontEndLibsCertV9', + [Certification.BackEndDevApisV9]: 'isBackEndDevApisCertV9', + [Certification.FullStackDeveloperV9]: 'isFullStackDeveloperCertV9', + [Certification.B1English]: 'isB1EnglishCert', + [Certification.A2Spanish]: 'isA2SpanishCert', + [Certification.A2Chinese]: 'isA2ChineseCert', + [Certification.A1Chinese]: 'isA1ChineseCert' +}; + +export type CertificationFlags = { + [key in UserCertFlag]: boolean; +}; // TODO: use i18n keys instead of hardcoded titles export const certToTitleMap: Record = {