diff --git a/apps/web/src/app/[locale]/(interviews)/(marketing)/page.tsx b/apps/web/src/app/[locale]/(interviews)/(marketing)/page.tsx index 4eb79fcd8..8f0294b8b 100644 --- a/apps/web/src/app/[locale]/(interviews)/(marketing)/page.tsx +++ b/apps/web/src/app/[locale]/(interviews)/(marketing)/page.tsx @@ -171,23 +171,28 @@ function getQuestionBankSectionData( ts: createQuestionData(language.ts), }; - const topicQuestionsData: Record< - QuestionTopic, - { - count: number; - duration: number; - questions: ReadonlyArray; - } + const topicQuestionsData: Partial< + Record< + QuestionTopic, + { + count: number; + duration: number; + questions: ReadonlyArray; + } + > > = { a11y: createQuestionData(topicQuestions.a11y), + async: createQuestionData(topicQuestions.async), + closure: createQuestionData(topicQuestions.closure), css: createQuestionData(topicQuestions.css), html: createQuestionData(topicQuestions.html), i18n: createQuestionData(topicQuestions.i18n), javascript: createQuestionData(topicQuestions.javascript), - network: createQuestionData(topicQuestions.network), + networking: createQuestionData(topicQuestions.networking), + oop: createQuestionData(topicQuestions.oop), performance: createQuestionData(topicQuestions.performance), security: createQuestionData(topicQuestions.security), - testing: createQuestionData(topicQuestions.testing), + 'web-api': createQuestionData(topicQuestions['web-api']), }; const formatQuestionsData: Record< diff --git a/apps/web/src/components/interviews/marketing/InterviewsMarketingPracticeQuestionBankSection.tsx b/apps/web/src/components/interviews/marketing/InterviewsMarketingPracticeQuestionBankSection.tsx index 2e31cae1e..13bc0ef6b 100644 --- a/apps/web/src/components/interviews/marketing/InterviewsMarketingPracticeQuestionBankSection.tsx +++ b/apps/web/src/components/interviews/marketing/InterviewsMarketingPracticeQuestionBankSection.tsx @@ -39,6 +39,7 @@ import { } from '~/components/ui/theme'; import useQuestionFormatFilter from '../questions/listings/filters/hooks/useQuestionFormatFilter'; +import useQuestionTopicLabels from '../questions/listings/filters/useQuestionTopicLabels'; type FilterType = 'format' | 'framework' | 'topics'; @@ -52,23 +53,43 @@ export type QuestionBankDataType = Readonly<{ format: Record; framework: Record; language: Record; - topic: Record; + topic: Partial>; }>; type Props = Readonly<{ questions: QuestionBankDataType; }>; -const topicRoute: Record = { - a11y: '/questions/quiz', +const selectedTopics = [ + 'a11y', + 'async', + 'css', + 'closure', + 'html', + 'i18n', + 'javascript', + 'networking', + 'oop', + 'performance', + 'security', + 'web-api', +] as const; + +type QuestionTopicToDisplay = (typeof selectedTopics)[number]; + +const topicHrefs: Record = { + a11y: '/questions/user-interface', + async: '/questions/quiz', + closure: '/questions/closure', css: '/questions/css', html: '/questions/html', i18n: '/questions/quiz', - javascript: '/questions/js', - network: '/questions/quiz', + javascript: '/questions/javascript', + networking: '/questions/quiz', + oop: '/questions/quiz', performance: '/questions/quiz', security: '/questions/quiz', - testing: '/questions/quiz', + 'web-api': '/questions/quiz', }; const MAX_TO_SHOW = 4; @@ -78,12 +99,13 @@ export default function InterviewsMarketingPracticeQuestionBankSection({ }: Props) { const intl = useIntl(); + const topics = useQuestionTopicLabels(); const [selectedFilter, setSelectedFilter] = useState('topics'); const [formatFilters, formatFilterOptions] = useQuestionFormatFilter({ initialValue: ['javascript'], }); const [topicFilters, topicFilterOptions] = useQuestionTopicFilter({ - initialValue: ['javascript'], + initialValue: ['a11y'], }); const [languageFilters, languageFilterOptions] = useQuestionLanguageFilter({ initialValue: ['html'], @@ -154,7 +176,10 @@ export default function InterviewsMarketingPracticeQuestionBankSection({ value: selectedFramework || selectedLanguage, }, topics: { - items: topicFilterOptions.options, + items: selectedTopics.map((topic) => ({ + label: topics[topic].label, + value: topic, + })), onClick: (value: string) => topicFilterOptions.setValues(new Set([value]) as Set), value: selectedTopic, @@ -171,7 +196,9 @@ export default function InterviewsMarketingPracticeQuestionBankSection({ const selectedRoute = (() => { switch (selectedFilter) { case 'topics': { - return topicRoute[selectedTopic]; + return ( + topicHrefs[selectedTopic as QuestionTopicToDisplay] ?? '/questions' + ); } case 'framework': { return ( @@ -193,7 +220,7 @@ export default function InterviewsMarketingPracticeQuestionBankSection({ return framework[selectedFramework] || language[selectedLanguage]; } case 'topics': { - return topic[selectedTopic]; + return topic[selectedTopic] ?? { count: 0, duration: 0, questions: [] }; } } } diff --git a/apps/web/src/components/interviews/questions/common/QuestionsTypes.ts b/apps/web/src/components/interviews/questions/common/QuestionsTypes.ts index a31a08a02..26bbbf186 100644 --- a/apps/web/src/components/interviews/questions/common/QuestionsTypes.ts +++ b/apps/web/src/components/interviews/questions/common/QuestionsTypes.ts @@ -161,14 +161,22 @@ export type QuestionMetadataWithCompletedStatus = QuestionMetadata & { export type QuestionTopic = | 'a11y' + | 'async' + | 'browser' + | 'closure' | 'css' + | 'graph' | 'html' | 'i18n' | 'javascript' - | 'network' + | 'networking' + | 'oop' | 'performance' + | 'recursion' | 'security' - | 'testing'; + | 'testing' + | 'tree' + | 'web-api'; export type QuestionQuiz = QuestionBase; diff --git a/apps/web/src/components/interviews/questions/listings/filters/hooks/useQuestionTopicFilter.ts b/apps/web/src/components/interviews/questions/listings/filters/hooks/useQuestionTopicFilter.ts index b1fc494b4..c8f76a870 100644 --- a/apps/web/src/components/interviews/questions/listings/filters/hooks/useQuestionTopicFilter.ts +++ b/apps/web/src/components/interviews/questions/listings/filters/hooks/useQuestionTopicFilter.ts @@ -14,14 +14,22 @@ import type { QuestionFilter } from '../QuestionFilterType'; // The lower the earlier it appears. const topicRanks: Record = { a11y: 3, + async: 0, + browser: 0, + closure: 0, css: 1, + graph: 0, html: 2, i18n: 40, javascript: 0, - network: 60, + networking: 60, + oop: 0, performance: 50, + recursion: 0, security: 80, testing: 99, + tree: 0, + 'web-api': 0, }; type Props = Readonly<{ @@ -77,7 +85,6 @@ export default function useQuestionTopicFilter( topicRanks[a as QuestionTopic] - topicRanks[b as QuestionTopic], ) .map((topic) => ({ - icon: topicLabels[topic as QuestionTopic].icon, label: topicLabels[topic as QuestionTopic].label, value: topic as QuestionTopic, })), diff --git a/apps/web/src/components/interviews/questions/listings/filters/useQuestionTopicLabels.ts b/apps/web/src/components/interviews/questions/listings/filters/useQuestionTopicLabels.ts index 0d18ef2c6..c5efe8a2c 100644 --- a/apps/web/src/components/interviews/questions/listings/filters/useQuestionTopicLabels.ts +++ b/apps/web/src/components/interviews/questions/listings/filters/useQuestionTopicLabels.ts @@ -1,15 +1,3 @@ -import { BiUniversalAccess } from 'react-icons/bi'; -import { - RiCss3Fill, - RiDashboard2Line, - RiHtml5Fill, - RiJavascriptFill, - RiLock2Line, - RiTestTubeLine, - RiTranslate2, - RiWifiLine, -} from 'react-icons/ri'; - import { useIntl } from '~/components/intl'; import type { QuestionTopic } from '../../common/QuestionsTypes'; @@ -20,80 +8,114 @@ export default function useQuestionTopicLabels() { const topicTitles: Record< QuestionTopic, Readonly<{ - icon: (props: React.ComponentProps<'svg'>) => JSX.Element; label: string; }> > = { a11y: { - icon: BiUniversalAccess, label: intl.formatMessage({ defaultMessage: 'Accessibility', - description: 'Accessibility topic for quiz questions', - id: 'q0+3Lk', + description: 'Front end development topic', + id: 'TbZTWa', + }), + }, + async: { + label: intl.formatMessage({ + defaultMessage: 'Async', + description: 'Front end development topic', + id: 'ezEYel', + }), + }, + browser: { + label: intl.formatMessage({ + defaultMessage: 'Browser', + description: 'Front end development topic', + id: '+4J3PK', + }), + }, + closure: { + label: intl.formatMessage({ + defaultMessage: 'Closure', + description: 'Front end development topic', + id: 'MMLnqG', }), }, css: { - icon: RiCss3Fill, + label: 'CSS', + }, + graph: { label: intl.formatMessage({ - defaultMessage: 'CSS', - description: 'CSS topic for quiz questions', - id: 'P4Or/u', + defaultMessage: 'Graph', + description: 'Front end development topic', + id: 'F8ULPJ', }), }, html: { - icon: RiHtml5Fill, - label: intl.formatMessage({ - defaultMessage: 'HTML', - description: 'HTML topic for quiz questions', - id: 'Yb2e9Q', - }), + label: 'HTML', }, i18n: { - icon: RiTranslate2, label: intl.formatMessage({ defaultMessage: 'Internationalization', - description: 'Internationalization topic for quiz questions', - id: 'tonRki', + description: 'Front end development topic', + id: 'Oykw81', }), }, javascript: { - icon: RiJavascriptFill, + label: 'JavaScript', + }, + networking: { label: intl.formatMessage({ - defaultMessage: 'JavaScript', - description: 'JavaScript topic for quiz questions', - id: 'w22UH7', + defaultMessage: 'Networking', + description: 'Front end development topic', + id: 'ZutazH', }), }, - network: { - icon: RiWifiLine, + oop: { label: intl.formatMessage({ - defaultMessage: 'Network', - description: 'Network topic for quiz questions', - id: 'pM/ZPq', + defaultMessage: 'Object-oriented programming', + description: 'Front end development topic', + id: 'KLoXYT', }), }, performance: { - icon: RiDashboard2Line, label: intl.formatMessage({ defaultMessage: 'Performance', - description: 'Performance topic for quiz questions', - id: 'kwblYW', + description: 'Front end development topic', + id: 'd1LTa+', + }), + }, + recursion: { + label: intl.formatMessage({ + defaultMessage: 'Recursion', + description: 'Front end development topic', + id: 'trRb9Z', }), }, security: { - icon: RiLock2Line, label: intl.formatMessage({ defaultMessage: 'Security', - description: 'Security topic for quiz questions', - id: 'kvLlxS', + description: 'Front end development topic', + id: '4MzF6x', }), }, testing: { - icon: RiTestTubeLine, label: intl.formatMessage({ defaultMessage: 'Testing', - description: 'Testing topic for quiz questions', - id: 'l9OWsu', + description: 'Front end development topic', + id: 'QwoKre', + }), + }, + tree: { + label: intl.formatMessage({ + defaultMessage: 'Tree', + description: 'Front end development topic', + id: 'Rs1Xi9', + }), + }, + 'web-api': { + label: intl.formatMessage({ + defaultMessage: 'Web APIs', + description: 'Front end development topic', + id: 'a1RZkG', }), }, }; diff --git a/apps/web/src/components/interviews/questions/metadata/QuestionTopicLabel.tsx b/apps/web/src/components/interviews/questions/metadata/QuestionTopicLabel.tsx index 302fadf34..af04b9070 100644 --- a/apps/web/src/components/interviews/questions/metadata/QuestionTopicLabel.tsx +++ b/apps/web/src/components/interviews/questions/metadata/QuestionTopicLabel.tsx @@ -8,16 +8,24 @@ import useQuestionTopicLabels from '../listings/filters/useQuestionTopicLabels'; const TopicLabelClasses: Record = { a11y: 'bg-pink-500 text-white dark:bg-neutral-800 dark:text-pink-500', + async: '', + browser: '', + closure: '', css: 'bg-sky-500 text-white dark:bg-neutral-800 dark:text-sky-500', + graph: '', html: 'bg-orange-600 text-white dark:bg-neutral-800 dark:text-orange-600', i18n: 'bg-neutral-700 text-white dark:bg-neutral-800 dark:text-neutral-300', javascript: 'bg-yellow-500 text-black dark:bg-neutral-800 dark:text-yellow-500', - network: 'bg-teal-400 text-white dark:bg-neutral-800 dark:text-teal-400', + networking: 'bg-teal-400 text-white dark:bg-neutral-800 dark:text-teal-400', + oop: '', performance: 'bg-indigo-500 text-white dark:bg-neutral-800 dark:text-indigo-400', + recursion: '', security: 'bg-red text-white dark:bg-neutral-800 dark:text-red', testing: 'bg-green-dark text-white dark:bg-neutral-800 dark:text-green', + tree: '', + 'web-api': '', }; export default function QuestionTopicLabel({ diff --git a/apps/web/src/data/QuestionLists.ts b/apps/web/src/data/QuestionLists.ts index 6f805a83d..4889e3880 100644 --- a/apps/web/src/data/QuestionLists.ts +++ b/apps/web/src/data/QuestionLists.ts @@ -192,7 +192,7 @@ export function useQuestionFormatsData(): QuestionFormatData { 'a11y', 'i18n', 'css', - 'network', + 'networking', 'security', 'testing', ], diff --git a/apps/web/src/db/QuestionsUtils.ts b/apps/web/src/db/QuestionsUtils.ts index 5ce5326e5..2bd1ec2b1 100644 --- a/apps/web/src/db/QuestionsUtils.ts +++ b/apps/web/src/db/QuestionsUtils.ts @@ -408,54 +408,37 @@ export function categorizeQuestionsByTopic( quizQuestions: ReadonlyArray; }>, ): Record> { - const categorizedQuestions: Record< - QuestionTopic, - ReadonlyArray - > = { + const categorizedQuestions: Record> = { a11y: [], + async: [], + browser: [], + closure: [], css: [], + graph: [], html: [], i18n: [], javascript: [], - network: [], + networking: [], + oop: [], performance: [], + recursion: [], security: [], testing: [], + tree: [], + 'web-api': [], }; const { codingQuestions, quizQuestions } = questions; - const LANGUAGE_TO_TOPIC: Record = { - css: 'css', - html: 'html', - js: 'javascript', - }; - function categorize(question: QuestionMetadata, type: 'coding' | 'quiz') { - if (type === 'coding') { - question.languages.forEach((language) => { - const topic = LANGUAGE_TO_TOPIC[language]; - - if (topic) { - categorizedQuestions[topic] = [ - ...categorizedQuestions[topic], - question, - ]; - } - }); - } else { - question.topics.forEach((topic) => { - if (categorizedQuestions[topic]) { - categorizedQuestions[topic] = [ - ...categorizedQuestions[topic], - question, - ]; - } - }); - } - } - - // Process all coding and quiz questions in a single pass - codingQuestions.forEach((question) => categorize(question, 'coding')); - quizQuestions.forEach((question) => categorize(question, 'quiz')); + codingQuestions.forEach((question) => { + question.topics.forEach((topic) => { + categorizedQuestions[topic].push(question); + }); + }); + quizQuestions.forEach((question) => { + question.topics.forEach((topic) => { + categorizedQuestions[topic].push(question); + }); + }); return categorizedQuestions; }