diff --git a/src/app/[language]/libraries/page.tsx b/src/app/[language]/libraries/page.tsx index 253148e9..08de1776 100644 --- a/src/app/[language]/libraries/page.tsx +++ b/src/app/[language]/libraries/page.tsx @@ -9,7 +9,6 @@ import { LibraryCategoryModel } from "@/features/libraries/models/library-catego import { DATA_PATH } from "@/libs/config/project-paths.constants"; import { DEFAULT_LANGUAGE_CODE } from "@/features/localization/localization.config"; import { getLibrariesDictionary } from "@/features/localization/services/language-dictionary.service"; -import { LIBRARIES_FILTER_DEFAULT_VALUE } from "@/libs/config/project.constants"; import { StructuredData } from "@/features/seo/components/structured-data.component"; import { generateArticleStructuredData } from "@/features/seo/services/structured-data.service"; import { PageMetadataProps } from "@/features/common/models/page-metadata.props"; @@ -18,11 +17,13 @@ import { generatePageMetadata } from "@/libs/metadata/metadata.service"; import { createUrlPath } from "@/libs/utils/path.utils"; import { siteTree } from "@/features/seo/site-tree"; import { getAuth0Dictionary } from "@/features/localization/services/ui-language-dictionary.service"; +import { LibraryModel } from "@/features/libraries/models/library.model"; +import { LibrariesDictionaryModel } from "@/features/localization/models/libraries-dictionary.model"; export async function generateMetadata({ params: { language }, }: PageMetadataProps): Promise { - const dictionary = getLibrariesDictionary(language); + const dictionary: LibrariesDictionaryModel = getLibrariesDictionary(language); return generatePageMetadata({ languageCode: language, @@ -37,7 +38,9 @@ export default function Libraries({ }: { params: { language: string }; searchParams?: { - filter?: string; + programming_language?: string; + algorithm?: keyof LibraryModel["support"]; + support?: keyof LibraryModel["support"]; }; }) { const librariesDictionary = getLibrariesDictionary(languageCode); @@ -47,19 +50,55 @@ export default function Libraries({ encoding: "utf-8", }); - const query: string | null = searchParams?.filter || ""; + const programmingLanguage = searchParams?.programming_language; + const algorithm = searchParams?.algorithm; + const support = searchParams?.support; + const query = programmingLanguage ?? algorithm ?? support ?? ""; const dictionary = JSON.parse(source) as LibraryDictionaryModel; + const allOptions = Object.keys(Object.values(dictionary)[0].libs[0].support); + const indexAlgorithmStart = allOptions.findIndex( + (option) => option === "hs256" + ); const categoryOptions: { id: string; name: string }[] = Object.values( - dictionary, + dictionary ).map((library) => ({ id: library.id, name: library.name, })); - let categories: LibraryCategoryModel[] = dictionary[query] - ? [dictionary[query]] - : Object.values(dictionary); + const supportOptions: { value: string; label: string }[] = allOptions + .slice(0, indexAlgorithmStart) + .map((key) => ({ + value: key, + label: key.toUpperCase(), + })); + + const algorithmOptions: { value: string; label: string }[] = allOptions + .slice(indexAlgorithmStart) + .map((key) => ({ + value: key, + label: key.toUpperCase(), + })); + + const categories: LibraryCategoryModel[] = + programmingLanguage && programmingLanguage !== "all" + ? [dictionary[programmingLanguage]] + : Object.values(dictionary); + + const categoryToFilter = algorithm ?? support; + + const filteredCategories = categoryToFilter + ? categories.map((category) => { + const filteredLibs = category.libs.filter( + (lib) => lib.support[categoryToFilter] + ); + return { + ...category, + libs: filteredLibs, + }; + }) + : categories; return ( <> @@ -166,11 +205,13 @@ export default function Libraries({ languageCode={languageCode} query={query || librariesDictionary.filterPicker.defaultValue.value} categoryOptions={categoryOptions} + algorithmOptions={algorithmOptions} + supportOptions={supportOptions} dictionary={librariesDictionary} /> + >, + selected: DebuggerPickerOptionModel +): LibraryFilterLabel | undefined => { + if (!Array.isArray(options)) return undefined; + + const group = (options as GroupBase[]).find( + (group) => group.options.some((opt) => opt.value === selected.value) + ); + return group ? group.label as LibraryFilterLabel : undefined; +}; + const PickerLabel: React.FC = ({ label }) => { return (
@@ -18,9 +35,16 @@ const PickerLabel: React.FC = ({ label }) => { interface DebuggerPickerComponentProps { label: string | null; languageCode: string; - options: DebuggerPickerOptionModel[]; + options: OptionsOrGroups< + DebuggerPickerOptionModel, + GroupBase + >; + isGrouped?: boolean; selectedOptionCode: DebuggerPickerOptionModel | null; - handleSelection: (value: string) => void; + handleSelection: ( + selection: string, + parentLabel?: LibraryFilterLabel + ) => void; placeholder: string | null; minWidth: string | null; } @@ -37,12 +61,14 @@ export const DebuggerPickerComponent: React.FC< }) => { const [isClient, setIsClient] = useState(false); - const handleChange = (selection: SingleValue) => { + const handleChange = ( + selection: SingleValue + ) => { if (!selection) { return; } - - handleSelection(selection.value); + const groupLabel = getGroupLabel(options, selection); + handleSelection(selection.value, groupLabel); }; useEffect(() => { diff --git a/src/features/common/models/debugger-picker-option.model.ts b/src/features/common/models/debugger-picker-option.model.ts index 15b9b7f6..87612f6e 100644 --- a/src/features/common/models/debugger-picker-option.model.ts +++ b/src/features/common/models/debugger-picker-option.model.ts @@ -1,5 +1,7 @@ +import { LibraryFilterLabel } from "@/features/libraries/models/library-filters.model"; + export interface DebuggerPickerOptionModel { value: any; - label: string; + label: string | LibraryFilterLabel; isDisabled?: boolean; } diff --git a/src/features/debugger/components/debugger-alg-picker/debugger-alg-picker.component.tsx b/src/features/debugger/components/debugger-alg-picker/debugger-alg-picker.component.tsx index b22fb781..f1d10216 100644 --- a/src/features/debugger/components/debugger-alg-picker/debugger-alg-picker.component.tsx +++ b/src/features/debugger/components/debugger-alg-picker/debugger-alg-picker.component.tsx @@ -11,7 +11,6 @@ import { useDecoderStore } from "@/features/decoder/services/decoder.store"; import { useDebuggerStore } from "@/features/debugger/services/debugger.store"; import { DebuggerWidgetValues } from "@/features/common/values/debugger-widget.values"; import { DebuggerPickerComponent } from "@/features/common/components/debugger-picker/debugger-picker.component"; -import { DebuggerPickerOptionModel } from "@/features/common/models/debugger-picker-option.model"; import { algDictionary, jwsExampleAlgHeaderParameterValuesDictionary, @@ -19,6 +18,7 @@ import { import { useButton } from "@react-aria/button"; import { clsx } from "clsx"; import { PrimaryFont } from "@/libs/theme/fonts"; +import { DebuggerPickerOptionModel } from "@/features/common/models/debugger-picker-option.model"; enum PickerStates { IDLE = "IDLE", diff --git a/src/features/libraries/components/library-hero/library-hero.component.tsx b/src/features/libraries/components/library-hero/library-hero.component.tsx index fccdde2a..1fda944e 100644 --- a/src/features/libraries/components/library-hero/library-hero.component.tsx +++ b/src/features/libraries/components/library-hero/library-hero.component.tsx @@ -3,17 +3,26 @@ import React, { useMemo } from "react"; import styles from "./library-hero.module.scss"; import { BoxComponent } from "@/features/common/components/box/box.component"; -import { usePathname, useRouter, useSearchParams } from "next/navigation"; -import { LIBRARIES_FILTER_QUERY_PARAM_KEY } from "@/libs/config/project.constants"; +import { usePathname, useRouter } from "next/navigation"; +import { + LIBRARIES_FILTER_ALGORITHM_KEY, + LIBRARIES_FILTER_PROGRAMMING_LANGUAGE_KEY, + LIBRARIES_FILTER_SUPPORT_KEY, +} from "@/libs/config/project.constants"; import { clsx } from "clsx"; import { getLocalizedSecondaryFont } from "@/libs/theme/fonts"; import { LibrariesDictionaryModel } from "@/features/localization/models/libraries-dictionary.model"; import { DebuggerPickerComponent } from "@/features/common/components/debugger-picker/debugger-picker.component"; +import { LibraryFilterLabel } from "../../models/library-filters.model"; +import { DebuggerPickerOptionModel } from "@/features/common/models/debugger-picker-option.model"; +import { GroupBase } from "react-select"; interface LibraryHeroComponentProps { languageCode: string; query: string; categoryOptions: { id: string; name: string }[]; + algorithmOptions: { value: string; label: string }[]; + supportOptions: { value: string; label: string }[]; dictionary: LibrariesDictionaryModel; } @@ -21,41 +30,69 @@ export const LibraryHeroComponent: React.FC = ({ languageCode, query, categoryOptions, + algorithmOptions, + supportOptions, dictionary, }) => { - const searchParams = useSearchParams(); const pathname = usePathname(); const { replace } = useRouter(); - const handleSelection = (selection: string) => { - const params = new URLSearchParams(searchParams); - - if (selection) { - params.set(LIBRARIES_FILTER_QUERY_PARAM_KEY, selection); - } else { - params.delete(LIBRARIES_FILTER_QUERY_PARAM_KEY); + const handleSelection = ( + selection: string, + parentLabel?: LibraryFilterLabel + ) => { + if (!parentLabel) { + return; + } + const params = new URLSearchParams(""); + switch (parentLabel) { + case "ProgrammingLanguage": + params.set(LIBRARIES_FILTER_PROGRAMMING_LANGUAGE_KEY, selection); + break; + case "Algorithm": + params.set(LIBRARIES_FILTER_ALGORITHM_KEY, selection); + break; + case "Support": + params.set(LIBRARIES_FILTER_SUPPORT_KEY, selection); + break; + default: + break; } - replace(`${pathname}?${params.toString()}`); }; const options = useMemo(() => { return [ { - value: dictionary.filterPicker.defaultValue.value, - label: dictionary.filterPicker.defaultValue.label, + label: "ProgrammingLanguage", + options: [ + { + value: dictionary.filterPicker.defaultValue.value, + label: dictionary.filterPicker.defaultValue.label, + }, + ...categoryOptions.map((categoryOption) => { + return { + value: categoryOption.id, + label: categoryOption.name, + }; + }), + ], + }, + { + label: "Support", + options: [...supportOptions], + }, + { + label: "Algorithm", + options: [...algorithmOptions], }, - ...categoryOptions.map((categoryOption) => { - return { - value: categoryOption.id, - label: categoryOption.name, - }; - }), - ]; + ] as GroupBase[]; }, [ categoryOptions, dictionary.filterPicker.defaultValue.label, dictionary.filterPicker.defaultValue.value, + algorithmOptions, + supportOptions ]); return ( @@ -67,7 +104,7 @@ export const LibraryHeroComponent: React.FC = ({

{dictionary.title} @@ -80,7 +117,9 @@ export const LibraryHeroComponent: React.FC = ({ languageCode={languageCode} options={options} selectedOptionCode={ - options.filter((option) => option.value === query)[0] + options + .flatMap((group) => group.options) + .filter((option) => option.value === query)[0] } handleSelection={handleSelection} placeholder={null} diff --git a/src/features/libraries/models/library-filters.model.ts b/src/features/libraries/models/library-filters.model.ts new file mode 100644 index 00000000..d8945ba3 --- /dev/null +++ b/src/features/libraries/models/library-filters.model.ts @@ -0,0 +1 @@ +export type LibraryFilterLabel = "ProgrammingLanguage" | "Algorithm" | "Support" \ No newline at end of file diff --git a/src/libs/config/project.constants.ts b/src/libs/config/project.constants.ts index e5cad71b..3303d5a8 100644 --- a/src/libs/config/project.constants.ts +++ b/src/libs/config/project.constants.ts @@ -1,6 +1,9 @@ export const BASE_URL = "https://jwt.io"; export const LIBRARIES_FILTER_QUERY_PARAM_KEY = "filter"; export const LIBRARIES_FILTER_DEFAULT_VALUE = "all"; +export const LIBRARIES_FILTER_PROGRAMMING_LANGUAGE_KEY = "programming_language"; +export const LIBRARIES_FILTER_ALGORITHM_KEY = "algorithm"; +export const LIBRARIES_FILTER_SUPPORT_KEY = "support"; export enum SupportedTokenHashParamValues { TOKEN = "token", ACCESS_TOKEN = "access_token",