Skip to content
Merged
59 changes: 50 additions & 9 deletions src/app/[language]/libraries/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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<Metadata> {
const dictionary = getLibrariesDictionary(language);
const dictionary: LibrariesDictionaryModel = getLibrariesDictionary(language);

return generatePageMetadata({
languageCode: language,
Expand All @@ -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);
Expand All @@ -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 (
<>
Expand Down Expand Up @@ -166,11 +205,13 @@ export default function Libraries({
languageCode={languageCode}
query={query || librariesDictionary.filterPicker.defaultValue.value}
categoryOptions={categoryOptions}
algorithmOptions={algorithmOptions}
supportOptions={supportOptions}
dictionary={librariesDictionary}
/>
<LibraryResultsComponent
languageCode={languageCode}
categories={categories}
categories={filteredCategories}
dictionary={librariesDictionary}
/>
<Auth0CtaComponent
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,29 @@
import React, { useEffect, useState } from "react";
import styles from "./debugger-picker.module.scss";
import Select, { SingleValue } from "react-select";
import Select, { SingleValue, OptionsOrGroups, GroupBase } from "react-select";
import { DebuggerPickerOptionModel } from "@/features/common/models/debugger-picker-option.model";
import { LibraryFilterLabel } from "@/features/libraries/models/library-filters.model";


interface PickerLabelProps {
label: string | null;
}

const getGroupLabel = (
options: OptionsOrGroups<
DebuggerPickerOptionModel,
GroupBase<DebuggerPickerOptionModel>
>,
selected: DebuggerPickerOptionModel
): LibraryFilterLabel | undefined => {
if (!Array.isArray(options)) return undefined;

const group = (options as GroupBase<DebuggerPickerOptionModel>[]).find(
(group) => group.options.some((opt) => opt.value === selected.value)
);
return group ? group.label as LibraryFilterLabel : undefined;
};

const PickerLabel: React.FC<PickerLabelProps> = ({ label }) => {
return (
<div className={styles.picker__label}>
Expand All @@ -18,9 +35,16 @@ const PickerLabel: React.FC<PickerLabelProps> = ({ label }) => {
interface DebuggerPickerComponentProps {
label: string | null;
languageCode: string;
options: DebuggerPickerOptionModel[];
options: OptionsOrGroups<
DebuggerPickerOptionModel,
GroupBase<DebuggerPickerOptionModel>
>;
isGrouped?: boolean;
selectedOptionCode: DebuggerPickerOptionModel | null;
handleSelection: (value: string) => void;
handleSelection: (
selection: string,
parentLabel?: LibraryFilterLabel
) => void;
placeholder: string | null;
minWidth: string | null;
}
Expand All @@ -37,12 +61,14 @@ export const DebuggerPickerComponent: React.FC<
}) => {
const [isClient, setIsClient] = useState(false);

const handleChange = (selection: SingleValue<DebuggerPickerOptionModel>) => {
const handleChange = (
selection: SingleValue<DebuggerPickerOptionModel>
) => {
if (!selection) {
return;
}

handleSelection(selection.value);
const groupLabel = getGroupLabel(options, selection);
handleSelection(selection.value, groupLabel);
};

useEffect(() => {
Expand Down
4 changes: 3 additions & 1 deletion src/features/common/models/debugger-picker-option.model.ts
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ 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,
} from "@/features/common/values/jws-alg-header-parameter-values.dictionary";
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",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,59 +3,96 @@
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;
}

export const LibraryHeroComponent: React.FC<LibraryHeroComponentProps> = ({
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<DebuggerPickerOptionModel>[];
}, [
categoryOptions,
dictionary.filterPicker.defaultValue.label,
dictionary.filterPicker.defaultValue.value,
algorithmOptions,
supportOptions
]);

return (
Expand All @@ -67,7 +104,7 @@ export const LibraryHeroComponent: React.FC<LibraryHeroComponentProps> = ({
<h1
className={clsx(
styles.heroTitle,
getLocalizedSecondaryFont(languageCode),
getLocalizedSecondaryFont(languageCode)
)}
>
{dictionary.title}
Expand All @@ -80,7 +117,9 @@ export const LibraryHeroComponent: React.FC<LibraryHeroComponentProps> = ({
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}
Expand Down
1 change: 1 addition & 0 deletions src/features/libraries/models/library-filters.model.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export type LibraryFilterLabel = "ProgrammingLanguage" | "Algorithm" | "Support"
3 changes: 3 additions & 0 deletions src/libs/config/project.constants.ts
Original file line number Diff line number Diff line change
@@ -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",
Expand Down