From 3594327d88e8443a682e2e9d10b5db90a74894bd Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Mon, 19 Sep 2022 15:22:23 +0300 Subject: [PATCH 1/7] TCA-441 - implement form input select --- .../lib/form/form-groups/form-input/index.ts | 1 + .../input-select/InputSelect.module.scss | 45 +++++++++ .../form-input/input-select/InputSelect.tsx | 99 +++++++++++++++++++ .../form-input/input-select/index.ts | 1 + .../input-wrapper/InputWrapper.module.scss | 1 + .../form-input/input-wrapper/InputWrapper.tsx | 11 ++- src-ts/lib/form/index.ts | 2 +- 7 files changed, 154 insertions(+), 6 deletions(-) create mode 100644 src-ts/lib/form/form-groups/form-input/input-select/InputSelect.module.scss create mode 100644 src-ts/lib/form/form-groups/form-input/input-select/InputSelect.tsx create mode 100644 src-ts/lib/form/form-groups/form-input/input-select/index.ts diff --git a/src-ts/lib/form/form-groups/form-input/index.ts b/src-ts/lib/form/form-groups/form-input/index.ts index 3f5dfa642..46f16f8c4 100644 --- a/src-ts/lib/form/form-groups/form-input/index.ts +++ b/src-ts/lib/form/form-groups/form-input/index.ts @@ -1,5 +1,6 @@ export * from './form-input-autcomplete-option.enum' export * from './input-rating' +export * from './input-select' export * from './input-text' export * from './input-textarea' export { inputOptional } from './input-wrapper' diff --git a/src-ts/lib/form/form-groups/form-input/input-select/InputSelect.module.scss b/src-ts/lib/form/form-groups/form-input/input-select/InputSelect.module.scss new file mode 100644 index 000000000..4911da867 --- /dev/null +++ b/src-ts/lib/form/form-groups/form-input/input-select/InputSelect.module.scss @@ -0,0 +1,45 @@ +@import '../../../../styles/includes'; + +.selected { + display: flex; + align-items: center; + margin-top: $space-xs; + cursor: pointer; + color: $black-100; + + &-icon { + margin-left: auto; + padding: $border-xs 0; + color: $turq-160; + > svg { + @include icon-size(14); + } + } +} + +.select-menu { + position: absolute; + top: calc(100% - 2px); + left: -1px; + right: -1px; + background: $tc-white; + border: $border-xs solid $black-40; + border-radius: 0 0 $space-xs $space-xs; + padding: $space-sm 0; +} + +.select-option { + font-weight: normal; + color: $black-100; + padding: $space-sm $space-lg; + + &:hover:global(:not(.selected)) { + background: $turq-160; + color: $tc-white; + cursor: pointer; + } + + &:global(.selected) { + font-weight: bold; + } +} diff --git a/src-ts/lib/form/form-groups/form-input/input-select/InputSelect.tsx b/src-ts/lib/form/form-groups/form-input/input-select/InputSelect.tsx new file mode 100644 index 000000000..b538a90c1 --- /dev/null +++ b/src-ts/lib/form/form-groups/form-input/input-select/InputSelect.tsx @@ -0,0 +1,99 @@ +import classNames from 'classnames' +import { + ChangeEvent, + FC, + ReactNode, + useState, + Dispatch, + SetStateAction, + MutableRefObject, + useRef, +} from 'react' +import { useClickOutside } from '../../../../hooks' +import { IconOutline } from '../../../../svgs' +import { InputWrapper } from '../input-wrapper' + +import styles from './InputSelect.module.scss' + +export interface InputSelectOption { + value: string + label?: ReactNode +} + +interface InputSelectProps { + readonly dirty?: boolean + readonly disabled?: boolean + readonly error?: string + readonly hideInlineErrors?: boolean + readonly name: string + readonly onChange: (event: ChangeEvent) => void + readonly tabIndex?: number + readonly value?: string + readonly options: Array + readonly label?: string + readonly hint?: string +} + +const InputSelect: FC = (props: InputSelectProps) => { + const triggerRef: MutableRefObject = useRef(undefined) + const [menuIsVisible, setMenuIsVisible]: [boolean, Dispatch>] = useState(false) + + const selectedOption = props.options.find(option => option.value === props.value); + + const label = (option?: InputSelectOption) => option ? option.label ?? option.value : '' + + const toggleMenu = () => setMenuIsVisible((wasVisible) => !wasVisible) + + const select = (option: InputSelectOption) => () => { + props.onChange({ + target: {value: option.value} , + } as unknown as ChangeEvent) + toggleMenu() + } + + useClickOutside(triggerRef.current, () => setMenuIsVisible(false)) + + return ( + +
+ {label(selectedOption)} + + + +
+ + {menuIsVisible && ( +
+ {props.options.map((option) => ( +
+ {label(option)} +
+ ))} +
+ )} + +
+ ) +} + +export default InputSelect diff --git a/src-ts/lib/form/form-groups/form-input/input-select/index.ts b/src-ts/lib/form/form-groups/form-input/input-select/index.ts new file mode 100644 index 000000000..605f28a24 --- /dev/null +++ b/src-ts/lib/form/form-groups/form-input/input-select/index.ts @@ -0,0 +1 @@ +export { default as InputSelect } from './InputSelect' diff --git a/src-ts/lib/form/form-groups/form-input/input-wrapper/InputWrapper.module.scss b/src-ts/lib/form/form-groups/form-input/input-wrapper/InputWrapper.module.scss index 6b212bb53..4052802ad 100644 --- a/src-ts/lib/form/form-groups/form-input/input-wrapper/InputWrapper.module.scss +++ b/src-ts/lib/form/form-groups/form-input/input-wrapper/InputWrapper.module.scss @@ -30,6 +30,7 @@ $error-line-height: 14px; box-sizing: border-box; border-radius: $space-xs; margin-bottom: calc($error-line-height + $space-xs); + position: relative; &.rating { border-color: transparent; diff --git a/src-ts/lib/form/form-groups/form-input/input-wrapper/InputWrapper.tsx b/src-ts/lib/form/form-groups/form-input/input-wrapper/InputWrapper.tsx index dff63ca4d..b51d6f32e 100644 --- a/src-ts/lib/form/form-groups/form-input/input-wrapper/InputWrapper.tsx +++ b/src-ts/lib/form/form-groups/form-input/input-wrapper/InputWrapper.tsx @@ -1,5 +1,5 @@ import classNames from 'classnames' -import { Dispatch, FC, ReactNode, SetStateAction, useState } from 'react' +import { Dispatch, forwardRef, ForwardRefExoticComponent, ReactNode, SetStateAction, useState } from 'react' import { IconSolid } from '../../../../svgs' @@ -16,11 +16,11 @@ interface InputWrapperProps { readonly hideInlineErrors?: boolean readonly hint?: string readonly label: string | JSX.Element - readonly tabIndex: number + readonly tabIndex?: number readonly type: 'checkbox' | 'password' | 'rating' | 'text' | 'textarea' } -const InputWrapper: FC = (props: InputWrapperProps) => { +const InputWrapper: ForwardRefExoticComponent = forwardRef((props: InputWrapperProps, ref) => { const [focusStyle, setFocusStyle]: [string | undefined, Dispatch>] = useState() @@ -44,7 +44,8 @@ const InputWrapper: FC = (props: InputWrapperProps) => { return (
= (props: InputWrapperProps) => { )}
) -} +}) export default InputWrapper diff --git a/src-ts/lib/form/index.ts b/src-ts/lib/form/index.ts index 4cc7d9d69..cc059c6f6 100644 --- a/src-ts/lib/form/index.ts +++ b/src-ts/lib/form/index.ts @@ -8,5 +8,5 @@ export { } from './form-functions' export * from './form-input.model' export * from './form-group.model' -export { inputOptional, FormInputAutocompleteOption } from './form-groups' +export * from './form-groups/form-input' export * from './validator-functions' From efa1d12652cb879180125335a1581be8a2658200 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Mon, 19 Sep 2022 16:28:55 +0300 Subject: [PATCH 2/7] TCA-441 - implement certificates sorting on tca landing page --- .../all-certifications-provider-data.model.ts | 2 +- .../all-certifications-sort-options.ts | 14 ++++ .../all-certifications.provider.tsx | 70 ++++++++++++++----- .../all-certifications-provider/index.ts | 1 + .../learn/welcome/WelcomePage.module.scss | 41 +++++++++-- src-ts/tools/learn/welcome/WelcomePage.tsx | 49 +++++++++++-- 6 files changed, 147 insertions(+), 30 deletions(-) create mode 100644 src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications-sort-options.ts diff --git a/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications-provider-data.model.ts b/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications-provider-data.model.ts index cc5d28762..68fa10988 100755 --- a/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications-provider-data.model.ts +++ b/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications-provider-data.model.ts @@ -2,8 +2,8 @@ import { LearnCertification } from './all-certifications-functions' export interface AllCertificationsProviderData { certification?: LearnCertification + allCertifications: Array certifications: Array - certificationsCount: number loading: boolean ready: boolean } diff --git a/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications-sort-options.ts b/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications-sort-options.ts new file mode 100644 index 000000000..41351850f --- /dev/null +++ b/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications-sort-options.ts @@ -0,0 +1,14 @@ +export const ALL_CERTIFICATIONS_SORT_ENUM = { + createdAt: 'Newest', + title: 'Title', + category: 'Category', +}; + +export type ALL_CERTIFICATIONS_SORT_FIELD_TYPE = keyof typeof ALL_CERTIFICATIONS_SORT_ENUM; + +export const ALL_CERTIFICATIONS_SORT_OPTIONS: Array<{value: ALL_CERTIFICATIONS_SORT_FIELD_TYPE, label: string}> = Object.entries( + ALL_CERTIFICATIONS_SORT_ENUM +).map(([value, label]) => ({value: value as ALL_CERTIFICATIONS_SORT_FIELD_TYPE, label})); + +export const ALL_CERTIFICATIONS_SORT_FIELDS: Array = Object.keys(ALL_CERTIFICATIONS_SORT_ENUM) as Array +export const ALL_CERTIFICATIONS_DEFAULT_SORT: ALL_CERTIFICATIONS_SORT_FIELD_TYPE = ALL_CERTIFICATIONS_SORT_OPTIONS[0].value; diff --git a/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications.provider.tsx b/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications.provider.tsx index 5bbb14210..b0d8126bb 100644 --- a/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications.provider.tsx +++ b/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications.provider.tsx @@ -1,10 +1,21 @@ -import { Dispatch, SetStateAction, useEffect, useState } from 'react' +import { orderBy } from 'lodash' +import { Dispatch, SetStateAction, useEffect, useState, useRef } from 'react' import { allCertificationsGetAsync, LearnCertification } from './all-certifications-functions' import { AllCertificationsProviderData } from './all-certifications-provider-data.model' +import { ALL_CERTIFICATIONS_DEFAULT_SORT, ALL_CERTIFICATIONS_SORT_FIELD_TYPE } from './all-certifications-sort-options' + +type SORT_DIRECTION = 'asc'|'desc' +const DEFAULT_SORT_DIRECTION: SORT_DIRECTION = 'desc' + interface CertificationsAllProviderOptions { enabled?: boolean + sort?: { + direction: SORT_DIRECTION, + field: ALL_CERTIFICATIONS_SORT_FIELD_TYPE + }, + filter?: undefined|'data-science'|'web-development'|'backend-development' } export function useAllCertifications( @@ -12,16 +23,47 @@ export function useAllCertifications( certificationId?: string, options?: CertificationsAllProviderOptions ): AllCertificationsProviderData { + const sort = useRef({ + field: ALL_CERTIFICATIONS_DEFAULT_SORT, + direction: DEFAULT_SORT_DIRECTION, + ...options?.sort, + }); + // const filter = useRef(options?.filter); const [state, setState]: [AllCertificationsProviderData, Dispatch>] = useState({ + allCertifications: [], certifications: [], - certificationsCount: 0, loading: false, ready: false, }) + function sortCertifications(certificates: Array, sortField: ALL_CERTIFICATIONS_SORT_FIELD_TYPE, sortDir: SORT_DIRECTION) { + return orderBy([...certificates], sortField, sortDir) + } + + function sortCertificates() { + setState((prevState) => ({ + ...prevState, + certifications: sortCertifications( + prevState.allCertifications, + sort.current.field, + sort.current.direction, + ), + })) + } + + if (options?.sort && (sort.current?.direction !== options?.sort?.direction || sort.current?.field !== options?.sort?.field)) { + sort.current = { + field: options?.sort?.field ?? ALL_CERTIFICATIONS_DEFAULT_SORT, + direction: options?.sort?.direction ?? DEFAULT_SORT_DIRECTION, + }; + + // wait to exit current render loop before triggering a new state update + setTimeout(sortCertificates) + } + useEffect(() => { setState((prevState) => ({ ...prevState, @@ -34,23 +76,17 @@ export function useAllCertifications( allCertificationsGetAsync(provider, certificationId) .then((certifications) => { - const certs: { - certification: LearnCertification; - certifications?: undefined; - } | { - certification?: undefined; - certifications: Array; - } = certificationId - ? { - certification: certifications as unknown as LearnCertification, - } - : { - certifications: [...certifications], - } + const sortedCertifications = sortCertifications( + certifications, + sort.current.field, + sort.current.direction, + ) + setState((prevState) => ({ ...prevState, - ...certs, - certificationsCount: certifications.length, + certifications: certificationId ? [] : sortedCertifications, + certification: !certificationId ? undefined : certifications as unknown as LearnCertification, + allCertifications: certificationId ? [] : [...certifications], loading: false, ready: true, })) diff --git a/src-ts/tools/learn/learn-lib/all-certifications-provider/index.ts b/src-ts/tools/learn/learn-lib/all-certifications-provider/index.ts index f8e61f839..1fb9b9c4a 100755 --- a/src-ts/tools/learn/learn-lib/all-certifications-provider/index.ts +++ b/src-ts/tools/learn/learn-lib/all-certifications-provider/index.ts @@ -1,3 +1,4 @@ export * from './all-certifications-functions' export * from './all-certifications-provider-data.model' +export * from './all-certifications-sort-options' export * from './all-certifications.provider' diff --git a/src-ts/tools/learn/welcome/WelcomePage.module.scss b/src-ts/tools/learn/welcome/WelcomePage.module.scss index c00834ead..97ee4655c 100644 --- a/src-ts/tools/learn/welcome/WelcomePage.module.scss +++ b/src-ts/tools/learn/welcome/WelcomePage.module.scss @@ -4,7 +4,7 @@ :global(.hero-card-col) { width: 43.5%; max-width: 600px; - + @include ltemd { width: 100%; max-width: none; @@ -15,7 +15,7 @@ .courses-section { padding: $space-xxxxl 0; position: relative; - + @include ltemd { padding-top: $space-xxl; } @@ -38,12 +38,43 @@ @include ltelg { grid-template-columns: repeat(2, 1fr); } - + @media (max-width: 576px) { grid-template-columns: repeat(1, 1fr); } - + @include ltemd { margin-top: $space-xxl; } -} \ No newline at end of file +} + +.courses-list-header { + display: flex; + align-items: center; + + > h3 { + display: flex; + align-items: center; + gap: $space-sm; + } +} + +.badge { + font-family: $font-roboto; + background: $blue-100; + + padding: 0 $space-sm; + border-radius: 50px; + color: $tc-white; +} + +.courses-list-filters { + display: flex; + margin-left: auto; + + gap: $space-xxl; + + > * { + min-width: 326px; + } +} diff --git a/src-ts/tools/learn/welcome/WelcomePage.tsx b/src-ts/tools/learn/welcome/WelcomePage.tsx index 12008470f..1b674c205 100644 --- a/src-ts/tools/learn/welcome/WelcomePage.tsx +++ b/src-ts/tools/learn/welcome/WelcomePage.tsx @@ -1,10 +1,13 @@ import classNames from 'classnames' -import { FC } from 'react' +import { Dispatch, FC, SetStateAction, useState } from 'react' -import { ContentLayout, LoadingSpinner, Portal } from '../../../lib' +import { ContentLayout, InputSelect, LoadingSpinner, Portal } from '../../../lib' import '../../../lib/styles/index.scss' import { AllCertificationsProviderData, + ALL_CERTIFICATIONS_DEFAULT_SORT, + ALL_CERTIFICATIONS_SORT_FIELD_TYPE, + ALL_CERTIFICATIONS_SORT_OPTIONS, useAllCertifications, UserCertificationsProviderData, useUserCertifications, @@ -17,7 +20,21 @@ import styles from './WelcomePage.module.scss' const WelcomePage: FC<{}> = () => { - const allCertsData: AllCertificationsProviderData = useAllCertifications() + const [sortField, setSortField]: [ + ALL_CERTIFICATIONS_SORT_FIELD_TYPE, + Dispatch> + ] = useState(ALL_CERTIFICATIONS_DEFAULT_SORT) + + const allCertsData: AllCertificationsProviderData = useAllCertifications( + undefined, + undefined, + { + sort: { + direction: sortField === 'createdAt' ? 'desc' : 'asc', + field: sortField + } + } + ) const userCertsData: UserCertificationsProviderData = useUserCertifications() const coursesReady: boolean = allCertsData.ready && userCertsData.ready @@ -42,7 +59,7 @@ const WelcomePage: FC<{}> = () => { theme='light' > = () => {
-

Courses Available

+
+

+ Courses Available + + {allCertsData.certifications.length} + +

+ + +
+ setSortField(e.target.value as ALL_CERTIFICATIONS_SORT_FIELD_TYPE)} + name='sort-courses' + label='Sort by' + > +
+
{coursesReady && (
{allCertsData.certifications - .map((certification) => ( + .map((certification, i) => ( From 8a1922d8d0da742da2f860dc96bfe5d06557879e Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Mon, 19 Sep 2022 16:51:28 +0300 Subject: [PATCH 3/7] add storage hook to keep state in sync with local storage --- src-ts/lib/hooks/index.ts | 1 + src-ts/lib/hooks/use-storage.hook.ts | 69 ++++++++++++++++++++++ src-ts/tools/learn/welcome/WelcomePage.tsx | 4 +- 3 files changed, 72 insertions(+), 2 deletions(-) create mode 100644 src-ts/lib/hooks/use-storage.hook.ts diff --git a/src-ts/lib/hooks/index.ts b/src-ts/lib/hooks/index.ts index a71c9300e..920935edc 100644 --- a/src-ts/lib/hooks/index.ts +++ b/src-ts/lib/hooks/index.ts @@ -1,4 +1,5 @@ export * from './use-check-is-mobile.hook' export * from './use-click-outside.hook' export * from './use-on-hover-element.hook' +export * from './use-storage.hook' export * from './use-window-size.hook' diff --git a/src-ts/lib/hooks/use-storage.hook.ts b/src-ts/lib/hooks/use-storage.hook.ts new file mode 100644 index 000000000..282601eeb --- /dev/null +++ b/src-ts/lib/hooks/use-storage.hook.ts @@ -0,0 +1,69 @@ +import { Dispatch, SetStateAction, useCallback, useState } from 'react' + +type StorageTypes = 'localStorage' | 'sessionStorage' + +export function useStorage( + storageType: StorageTypes, + storageKey: string, + initialValue?: T +): [T, Dispatch>] { + const storage: Storage = window[storageType] + + const readStoredValue: () => T = useCallback(() => { + try { + // Get from local storage by key + const item: string | null = storage.getItem(storageKey) + // Parse stored json or if none return initialValue + return item ? JSON.parse(item) : initialValue + } catch (error) { + // If error also return value + return initialValue + } + }, [storage, storageKey, initialValue]) + + // State to store our value + // Pass initial state function to useState so logic is only executed once + const [storedValue, setStoredValue]: [T, Dispatch>] = useState(readStoredValue()) + + // Return a wrapped version of useState's setter function that + // persists the new value to local or session storage. + const setValue: Dispatch> = useCallback((value: T) => { + try { + // Allow value to be a function so we have same API as useState + setStoredValue((storedv: T) => { + const valueToStore: T = value instanceof Function ? value(storedv) : value + + if (valueToStore === undefined) { + storage.removeItem(storageKey) + } else { + // Save to local storage + storage.setItem(storageKey, JSON.stringify(valueToStore)) + } + + return valueToStore + }) + } catch (error) { + // A more advanced implementation would handle the error case + // tslint:disable-next-line:no-console + console.error(error) + } + }, [storage, storageKey]) as Dispatch> + + return [storedValue, setValue] +} + +export const useLocalStorage: ( + key: string, + initialValue?: T +) => [T, Dispatch>] = ( + key: string, + initialValue?: T +) => useStorage('localStorage', key, initialValue) + +export const useSessionStorage: ( + key: string, + initialValue?: T +) => [T, Dispatch>] = ( + key: string, + initialValue?: T +) => useStorage('sessionStorage', key, initialValue) diff --git a/src-ts/tools/learn/welcome/WelcomePage.tsx b/src-ts/tools/learn/welcome/WelcomePage.tsx index 1b674c205..aedb00554 100644 --- a/src-ts/tools/learn/welcome/WelcomePage.tsx +++ b/src-ts/tools/learn/welcome/WelcomePage.tsx @@ -1,7 +1,7 @@ import classNames from 'classnames' import { Dispatch, FC, SetStateAction, useState } from 'react' -import { ContentLayout, InputSelect, LoadingSpinner, Portal } from '../../../lib' +import { ContentLayout, InputSelect, LoadingSpinner, Portal, useLocalStorage } from '../../../lib' import '../../../lib/styles/index.scss' import { AllCertificationsProviderData, @@ -23,7 +23,7 @@ const WelcomePage: FC<{}> = () => { const [sortField, setSortField]: [ ALL_CERTIFICATIONS_SORT_FIELD_TYPE, Dispatch> - ] = useState(ALL_CERTIFICATIONS_DEFAULT_SORT) + ] = useLocalStorage('tca-welcome-sort-certs', ALL_CERTIFICATIONS_DEFAULT_SORT) const allCertsData: AllCertificationsProviderData = useAllCertifications( undefined, From fcacff39defc5bed66aa19519104b0a407ab161b Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Mon, 19 Sep 2022 17:01:07 +0300 Subject: [PATCH 4/7] lint fixes --- .../form-input/input-select/InputSelect.tsx | 39 +++++++++++-------- .../all-certifications-provider-data.model.ts | 2 +- .../all-certifications-sort-options.ts | 18 +++++---- .../all-certifications.provider.tsx | 35 ++++++++++------- src-ts/tools/learn/welcome/WelcomePage.tsx | 7 ++-- 5 files changed, 57 insertions(+), 44 deletions(-) diff --git a/src-ts/lib/form/form-groups/form-input/input-select/InputSelect.tsx b/src-ts/lib/form/form-groups/form-input/input-select/InputSelect.tsx index b538a90c1..602f09e48 100644 --- a/src-ts/lib/form/form-groups/form-input/input-select/InputSelect.tsx +++ b/src-ts/lib/form/form-groups/form-input/input-select/InputSelect.tsx @@ -1,14 +1,15 @@ import classNames from 'classnames' import { ChangeEvent, + Dispatch, FC, + MutableRefObject, ReactNode, - useState, - Dispatch, SetStateAction, - MutableRefObject, useRef, + useState, } from 'react' + import { useClickOutside } from '../../../../hooks' import { IconOutline } from '../../../../svgs' import { InputWrapper } from '../input-wrapper' @@ -16,8 +17,8 @@ import { InputWrapper } from '../input-wrapper' import styles from './InputSelect.module.scss' export interface InputSelectOption { - value: string label?: ReactNode + value: string } interface InputSelectProps { @@ -25,26 +26,28 @@ interface InputSelectProps { readonly disabled?: boolean readonly error?: string readonly hideInlineErrors?: boolean + readonly hint?: string + readonly label?: string readonly name: string readonly onChange: (event: ChangeEvent) => void + readonly options: Array readonly tabIndex?: number readonly value?: string - readonly options: Array - readonly label?: string - readonly hint?: string } const InputSelect: FC = (props: InputSelectProps) => { const triggerRef: MutableRefObject = useRef(undefined) const [menuIsVisible, setMenuIsVisible]: [boolean, Dispatch>] = useState(false) - const selectedOption = props.options.find(option => option.value === props.value); + const selectedOption: InputSelectOption | undefined = props.options.find(option => option.value === props.value) - const label = (option?: InputSelectOption) => option ? option.label ?? option.value : '' + const label: (option: InputSelectOption) => ReactNode = (option?: InputSelectOption) => ( + option ? option.label ?? option.value : '' + ) - const toggleMenu = () => setMenuIsVisible((wasVisible) => !wasVisible) + const toggleMenu: () => void = () => setMenuIsVisible((wasVisible) => !wasVisible) - const select = (option: InputSelectOption) => () => { + const select: (option: InputSelectOption) => () => void = (option: InputSelectOption) => () => { props.onChange({ target: {value: option.value} , } as unknown as ChangeEvent) @@ -65,12 +68,14 @@ const InputSelect: FC = (props: InputSelectProps) => { hideInlineErrors={props.hideInlineErrors} ref={triggerRef} > -
- {label(selectedOption)} - - - -
+ {selectedOption && ( +
+ {label(selectedOption)} + + + +
+ )} {menuIsVisible && (
diff --git a/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications-provider-data.model.ts b/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications-provider-data.model.ts index 68fa10988..902cfedea 100755 --- a/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications-provider-data.model.ts +++ b/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications-provider-data.model.ts @@ -1,8 +1,8 @@ import { LearnCertification } from './all-certifications-functions' export interface AllCertificationsProviderData { - certification?: LearnCertification allCertifications: Array + certification?: LearnCertification certifications: Array loading: boolean ready: boolean diff --git a/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications-sort-options.ts b/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications-sort-options.ts index 41351850f..1a3ca572d 100644 --- a/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications-sort-options.ts +++ b/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications-sort-options.ts @@ -1,14 +1,18 @@ -export const ALL_CERTIFICATIONS_SORT_ENUM = { +export const ALL_CERTIFICATIONS_SORT_ENUM: { + category: string, + createdAt: string, + title: string, +} = { + category: 'Category', createdAt: 'Newest', title: 'Title', - category: 'Category', -}; +} -export type ALL_CERTIFICATIONS_SORT_FIELD_TYPE = keyof typeof ALL_CERTIFICATIONS_SORT_ENUM; +export type ALL_CERTIFICATIONS_SORT_FIELD_TYPE = keyof typeof ALL_CERTIFICATIONS_SORT_ENUM -export const ALL_CERTIFICATIONS_SORT_OPTIONS: Array<{value: ALL_CERTIFICATIONS_SORT_FIELD_TYPE, label: string}> = Object.entries( +export const ALL_CERTIFICATIONS_SORT_OPTIONS: Array<{label: string, value: ALL_CERTIFICATIONS_SORT_FIELD_TYPE, }> = Object.entries( ALL_CERTIFICATIONS_SORT_ENUM -).map(([value, label]) => ({value: value as ALL_CERTIFICATIONS_SORT_FIELD_TYPE, label})); +).map(([value, label]) => ({value: value as ALL_CERTIFICATIONS_SORT_FIELD_TYPE, label})) export const ALL_CERTIFICATIONS_SORT_FIELDS: Array = Object.keys(ALL_CERTIFICATIONS_SORT_ENUM) as Array -export const ALL_CERTIFICATIONS_DEFAULT_SORT: ALL_CERTIFICATIONS_SORT_FIELD_TYPE = ALL_CERTIFICATIONS_SORT_OPTIONS[0].value; +export const ALL_CERTIFICATIONS_DEFAULT_SORT: ALL_CERTIFICATIONS_SORT_FIELD_TYPE = ALL_CERTIFICATIONS_SORT_OPTIONS[0].value diff --git a/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications.provider.tsx b/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications.provider.tsx index b0d8126bb..9c7d02eda 100644 --- a/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications.provider.tsx +++ b/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications.provider.tsx @@ -1,5 +1,5 @@ import { orderBy } from 'lodash' -import { Dispatch, SetStateAction, useEffect, useState, useRef } from 'react' +import { Dispatch, MutableRefObject, SetStateAction, useEffect, useRef, useState } from 'react' import { allCertificationsGetAsync, LearnCertification } from './all-certifications-functions' import { AllCertificationsProviderData } from './all-certifications-provider-data.model' @@ -8,14 +8,15 @@ import { ALL_CERTIFICATIONS_DEFAULT_SORT, ALL_CERTIFICATIONS_SORT_FIELD_TYPE } f type SORT_DIRECTION = 'asc'|'desc' const DEFAULT_SORT_DIRECTION: SORT_DIRECTION = 'desc' +interface CertificationsAllProviderSortOptions { + direction: SORT_DIRECTION, + field: ALL_CERTIFICATIONS_SORT_FIELD_TYPE +} interface CertificationsAllProviderOptions { enabled?: boolean - sort?: { - direction: SORT_DIRECTION, - field: ALL_CERTIFICATIONS_SORT_FIELD_TYPE - }, filter?: undefined|'data-science'|'web-development'|'backend-development' + sort?: CertificationsAllProviderSortOptions } export function useAllCertifications( @@ -23,11 +24,11 @@ export function useAllCertifications( certificationId?: string, options?: CertificationsAllProviderOptions ): AllCertificationsProviderData { - const sort = useRef({ - field: ALL_CERTIFICATIONS_DEFAULT_SORT, + const sort: MutableRefObject = useRef({ direction: DEFAULT_SORT_DIRECTION, + field: ALL_CERTIFICATIONS_DEFAULT_SORT, ...options?.sort, - }); + }) // const filter = useRef(options?.filter); const [state, setState]: @@ -39,11 +40,15 @@ export function useAllCertifications( ready: false, }) - function sortCertifications(certificates: Array, sortField: ALL_CERTIFICATIONS_SORT_FIELD_TYPE, sortDir: SORT_DIRECTION) { + function sortCertifications( + certificates: Array, + sortField: ALL_CERTIFICATIONS_SORT_FIELD_TYPE, + sortDir: SORT_DIRECTION + ): Array { return orderBy([...certificates], sortField, sortDir) } - function sortCertificates() { + function sortCertificates(): void { setState((prevState) => ({ ...prevState, certifications: sortCertifications( @@ -56,9 +61,9 @@ export function useAllCertifications( if (options?.sort && (sort.current?.direction !== options?.sort?.direction || sort.current?.field !== options?.sort?.field)) { sort.current = { - field: options?.sort?.field ?? ALL_CERTIFICATIONS_DEFAULT_SORT, direction: options?.sort?.direction ?? DEFAULT_SORT_DIRECTION, - }; + field: options?.sort?.field ?? ALL_CERTIFICATIONS_DEFAULT_SORT, + } // wait to exit current render loop before triggering a new state update setTimeout(sortCertificates) @@ -76,7 +81,7 @@ export function useAllCertifications( allCertificationsGetAsync(provider, certificationId) .then((certifications) => { - const sortedCertifications = sortCertifications( + const sortedCertifications: Array = sortCertifications( certifications, sort.current.field, sort.current.direction, @@ -84,9 +89,9 @@ export function useAllCertifications( setState((prevState) => ({ ...prevState, - certifications: certificationId ? [] : sortedCertifications, - certification: !certificationId ? undefined : certifications as unknown as LearnCertification, allCertifications: certificationId ? [] : [...certifications], + certification: !certificationId ? undefined : certifications as unknown as LearnCertification, + certifications: certificationId ? [] : sortedCertifications, loading: false, ready: true, })) diff --git a/src-ts/tools/learn/welcome/WelcomePage.tsx b/src-ts/tools/learn/welcome/WelcomePage.tsx index aedb00554..c19cd12ee 100644 --- a/src-ts/tools/learn/welcome/WelcomePage.tsx +++ b/src-ts/tools/learn/welcome/WelcomePage.tsx @@ -1,5 +1,5 @@ import classNames from 'classnames' -import { Dispatch, FC, SetStateAction, useState } from 'react' +import { Dispatch, FC, SetStateAction } from 'react' import { ContentLayout, InputSelect, LoadingSpinner, Portal, useLocalStorage } from '../../../lib' import '../../../lib/styles/index.scss' @@ -31,8 +31,8 @@ const WelcomePage: FC<{}> = () => { { sort: { direction: sortField === 'createdAt' ? 'desc' : 'asc', - field: sortField - } + field: sortField, + }, } ) const userCertsData: UserCertificationsProviderData = useUserCertifications() @@ -78,7 +78,6 @@ const WelcomePage: FC<{}> = () => { -
Date: Mon, 19 Sep 2022 18:12:21 +0300 Subject: [PATCH 5/7] TCA-441 - add certifications filter by category on landing page --- .../input-select/InputSelect.module.scss | 2 + .../all-certifications-sort-options.ts | 18 ---- .../all-certifications.provider.tsx | 83 ++++++++++--------- .../all-certifications-provider/index.ts | 1 - src-ts/tools/learn/welcome/WelcomePage.tsx | 49 +++++++++-- 5 files changed, 87 insertions(+), 66 deletions(-) delete mode 100644 src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications-sort-options.ts diff --git a/src-ts/lib/form/form-groups/form-input/input-select/InputSelect.module.scss b/src-ts/lib/form/form-groups/form-input/input-select/InputSelect.module.scss index 4911da867..bf55e8a28 100644 --- a/src-ts/lib/form/form-groups/form-input/input-select/InputSelect.module.scss +++ b/src-ts/lib/form/form-groups/form-input/input-select/InputSelect.module.scss @@ -26,6 +26,8 @@ border: $border-xs solid $black-40; border-radius: 0 0 $space-xs $space-xs; padding: $space-sm 0; + max-height: 230px; + overflow: auto; } .select-option { diff --git a/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications-sort-options.ts b/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications-sort-options.ts deleted file mode 100644 index 1a3ca572d..000000000 --- a/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications-sort-options.ts +++ /dev/null @@ -1,18 +0,0 @@ -export const ALL_CERTIFICATIONS_SORT_ENUM: { - category: string, - createdAt: string, - title: string, -} = { - category: 'Category', - createdAt: 'Newest', - title: 'Title', -} - -export type ALL_CERTIFICATIONS_SORT_FIELD_TYPE = keyof typeof ALL_CERTIFICATIONS_SORT_ENUM - -export const ALL_CERTIFICATIONS_SORT_OPTIONS: Array<{label: string, value: ALL_CERTIFICATIONS_SORT_FIELD_TYPE, }> = Object.entries( - ALL_CERTIFICATIONS_SORT_ENUM -).map(([value, label]) => ({value: value as ALL_CERTIFICATIONS_SORT_FIELD_TYPE, label})) - -export const ALL_CERTIFICATIONS_SORT_FIELDS: Array = Object.keys(ALL_CERTIFICATIONS_SORT_ENUM) as Array -export const ALL_CERTIFICATIONS_DEFAULT_SORT: ALL_CERTIFICATIONS_SORT_FIELD_TYPE = ALL_CERTIFICATIONS_SORT_OPTIONS[0].value diff --git a/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications.provider.tsx b/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications.provider.tsx index 9c7d02eda..65ace83d9 100644 --- a/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications.provider.tsx +++ b/src-ts/tools/learn/learn-lib/all-certifications-provider/all-certifications.provider.tsx @@ -1,21 +1,22 @@ -import { orderBy } from 'lodash' +import { filter as filterBy, orderBy } from 'lodash' import { Dispatch, MutableRefObject, SetStateAction, useEffect, useRef, useState } from 'react' import { allCertificationsGetAsync, LearnCertification } from './all-certifications-functions' import { AllCertificationsProviderData } from './all-certifications-provider-data.model' -import { ALL_CERTIFICATIONS_DEFAULT_SORT, ALL_CERTIFICATIONS_SORT_FIELD_TYPE } from './all-certifications-sort-options' - -type SORT_DIRECTION = 'asc'|'desc' -const DEFAULT_SORT_DIRECTION: SORT_DIRECTION = 'desc' interface CertificationsAllProviderSortOptions { - direction: SORT_DIRECTION, - field: ALL_CERTIFICATIONS_SORT_FIELD_TYPE + direction: 'asc'|'desc', + field: keyof LearnCertification +} + +interface CertificationsAllProviderFilterOptions { + field: keyof LearnCertification, + value: string } interface CertificationsAllProviderOptions { enabled?: boolean - filter?: undefined|'data-science'|'web-development'|'backend-development' + filter?: CertificationsAllProviderFilterOptions sort?: CertificationsAllProviderSortOptions } @@ -24,12 +25,8 @@ export function useAllCertifications( certificationId?: string, options?: CertificationsAllProviderOptions ): AllCertificationsProviderData { - const sort: MutableRefObject = useRef({ - direction: DEFAULT_SORT_DIRECTION, - field: ALL_CERTIFICATIONS_DEFAULT_SORT, - ...options?.sort, - }) - // const filter = useRef(options?.filter); + const sort: MutableRefObject = useRef(options?.sort) + const filter: MutableRefObject = useRef(options?.filter) const [state, setState]: [AllCertificationsProviderData, Dispatch>] @@ -40,33 +37,46 @@ export function useAllCertifications( ready: false, }) - function sortCertifications( - certificates: Array, - sortField: ALL_CERTIFICATIONS_SORT_FIELD_TYPE, - sortDir: SORT_DIRECTION + function getSortedCertifications( + certificates: Array ): Array { - return orderBy([...certificates], sortField, sortDir) + return !sort.current + ? certificates + : orderBy([...certificates], sort.current.field, sort.current.direction) } - function sortCertificates(): void { - setState((prevState) => ({ + function getFilteredCertifications( + certificates: Array + ): Array { + return !filter.current?.value + ? certificates + : filterBy([...certificates], {[filter.current.field]: filter.current.value}) + } + + function getFilteredAndSortedCertifications( + certificates: Array + ): Array { + return getSortedCertifications(getFilteredCertifications(certificates)) + } + + if (sort.current?.direction !== options?.sort?.direction || sort.current?.field !== options?.sort?.field) { + sort.current = options?.sort ? { ...options?.sort } : undefined + + // wait to exit current render loop before triggering a new state update + setTimeout(() => setState((prevState) => ({ ...prevState, - certifications: sortCertifications( - prevState.allCertifications, - sort.current.field, - sort.current.direction, - ), - })) + certifications: getFilteredAndSortedCertifications(prevState.allCertifications), + }))) } - if (options?.sort && (sort.current?.direction !== options?.sort?.direction || sort.current?.field !== options?.sort?.field)) { - sort.current = { - direction: options?.sort?.direction ?? DEFAULT_SORT_DIRECTION, - field: options?.sort?.field ?? ALL_CERTIFICATIONS_DEFAULT_SORT, - } + if (filter.current?.field !== options?.filter?.field || filter.current?.value !== options?.filter?.value) { + filter.current = options?.filter ? { ...options?.filter } : undefined // wait to exit current render loop before triggering a new state update - setTimeout(sortCertificates) + setTimeout(() => setState((prevState) => ({ + ...prevState, + certifications: getFilteredAndSortedCertifications(prevState.allCertifications), + }))) } useEffect(() => { @@ -81,11 +91,8 @@ export function useAllCertifications( allCertificationsGetAsync(provider, certificationId) .then((certifications) => { - const sortedCertifications: Array = sortCertifications( - certifications, - sort.current.field, - sort.current.direction, - ) + const filteredCertifications: Array = getFilteredCertifications(certifications) + const sortedCertifications: Array = getSortedCertifications(filteredCertifications) setState((prevState) => ({ ...prevState, diff --git a/src-ts/tools/learn/learn-lib/all-certifications-provider/index.ts b/src-ts/tools/learn/learn-lib/all-certifications-provider/index.ts index 1fb9b9c4a..f8e61f839 100755 --- a/src-ts/tools/learn/learn-lib/all-certifications-provider/index.ts +++ b/src-ts/tools/learn/learn-lib/all-certifications-provider/index.ts @@ -1,4 +1,3 @@ export * from './all-certifications-functions' export * from './all-certifications-provider-data.model' -export * from './all-certifications-sort-options' export * from './all-certifications.provider' diff --git a/src-ts/tools/learn/welcome/WelcomePage.tsx b/src-ts/tools/learn/welcome/WelcomePage.tsx index c19cd12ee..2d4542181 100644 --- a/src-ts/tools/learn/welcome/WelcomePage.tsx +++ b/src-ts/tools/learn/welcome/WelcomePage.tsx @@ -1,13 +1,12 @@ import classNames from 'classnames' -import { Dispatch, FC, SetStateAction } from 'react' +import { uniq } from 'lodash' +import { Dispatch, FC, SetStateAction, useMemo } from 'react' import { ContentLayout, InputSelect, LoadingSpinner, Portal, useLocalStorage } from '../../../lib' import '../../../lib/styles/index.scss' import { AllCertificationsProviderData, - ALL_CERTIFICATIONS_DEFAULT_SORT, - ALL_CERTIFICATIONS_SORT_FIELD_TYPE, - ALL_CERTIFICATIONS_SORT_OPTIONS, + LearnCertification, useAllCertifications, UserCertificationsProviderData, useUserCertifications, @@ -18,17 +17,34 @@ import { CoursesCard } from './courses-card' import { ProgressBlock } from './progress-block' import styles from './WelcomePage.module.scss' +type SORT_FIELD_TYPE = keyof LearnCertification +const SORT_OPTIONS: Array<{label: string, value: SORT_FIELD_TYPE}> = [ + {label: 'Category', value: 'category'}, + {label: 'Newest', value: 'createdAt'}, + {label: 'Title', value: 'title'}, +] +export const DEFAULT_SORT: SORT_FIELD_TYPE = SORT_OPTIONS[0].value + const WelcomePage: FC<{}> = () => { const [sortField, setSortField]: [ - ALL_CERTIFICATIONS_SORT_FIELD_TYPE, - Dispatch> - ] = useLocalStorage('tca-welcome-sort-certs', ALL_CERTIFICATIONS_DEFAULT_SORT) + SORT_FIELD_TYPE, + Dispatch> + ] = useLocalStorage('tca-welcome-sort-certs', DEFAULT_SORT) + + const [selectedCategory, setSelectedCategory]: [ + string, + Dispatch> + ] = useLocalStorage('tca-welcome-filter-certs', '') const allCertsData: AllCertificationsProviderData = useAllCertifications( undefined, undefined, { + filter: { + field: 'category', + value: selectedCategory, + }, sort: { direction: sortField === 'createdAt' ? 'desc' : 'asc', field: sortField, @@ -39,6 +55,14 @@ const WelcomePage: FC<{}> = () => { const coursesReady: boolean = allCertsData.ready && userCertsData.ready + const certsCategoriesOptions: Array<{label: string, value: string}> = useMemo(() => { + const certsCategories: Array = uniq(allCertsData.allCertifications.map(c => c.category)) + return [ + {label: 'All Categories', value: ''}, + ...certsCategories.map((c) => ({value: c, label: c})), + ] + }, [allCertsData]) + return ( @@ -80,9 +104,16 @@ const WelcomePage: FC<{}> = () => {
setSelectedCategory(e.target.value as string)} + name='filter-courses' + label='Categories' + > + setSortField(e.target.value as ALL_CERTIFICATIONS_SORT_FIELD_TYPE)} + onChange={(e) => setSortField(e.target.value as SORT_FIELD_TYPE)} name='sort-courses' label='Sort by' > From 186fe43be839a2a68ffdcc374e31bb923e5d4162 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Tue, 20 Sep 2022 11:54:15 +0300 Subject: [PATCH 6/7] TCA-441 - learn landing filters - mobile UI --- .../input-select/InputSelect.module.scss | 12 ++++- .../form-input/input-select/InputSelect.tsx | 54 +++++++++---------- .../form-input/input-wrapper/InputWrapper.tsx | 3 +- .../learn/welcome/WelcomePage.module.scss | 24 +++++++++ 4 files changed, 64 insertions(+), 29 deletions(-) diff --git a/src-ts/lib/form/form-groups/form-input/input-select/InputSelect.module.scss b/src-ts/lib/form/form-groups/form-input/input-select/InputSelect.module.scss index bf55e8a28..7d4c73668 100644 --- a/src-ts/lib/form/form-groups/form-input/input-select/InputSelect.module.scss +++ b/src-ts/lib/form/form-groups/form-input/input-select/InputSelect.module.scss @@ -17,9 +17,19 @@ } } -.select-menu { +.menu-wrap { position: absolute; top: calc(100% - 2px); + left: 0; + width: 100%; + &:not(:empty) { + z-index: 9; + } +} + +.select-menu { + position: absolute; + top: 100%; left: -1px; right: -1px; background: $tc-white; diff --git a/src-ts/lib/form/form-groups/form-input/input-select/InputSelect.tsx b/src-ts/lib/form/form-groups/form-input/input-select/InputSelect.tsx index 602f09e48..f9da6acf8 100644 --- a/src-ts/lib/form/form-groups/form-input/input-select/InputSelect.tsx +++ b/src-ts/lib/form/form-groups/form-input/input-select/InputSelect.tsx @@ -68,34 +68,34 @@ const InputSelect: FC = (props: InputSelectProps) => { hideInlineErrors={props.hideInlineErrors} ref={triggerRef} > - {selectedOption && ( -
- {label(selectedOption)} - - - -
- )} +
+ {selectedOption ? label(selectedOption) : ''} + + + +
- {menuIsVisible && ( -
- {props.options.map((option) => ( -
- {label(option)} -
- ))} -
- )} +
+ {menuIsVisible && ( +
+ {props.options.map((option) => ( +
+ {label(option)} +
+ ))} +
+ )} +
) diff --git a/src-ts/lib/form/form-groups/form-input/input-wrapper/InputWrapper.tsx b/src-ts/lib/form/form-groups/form-input/input-wrapper/InputWrapper.tsx index b51d6f32e..f3468cb49 100644 --- a/src-ts/lib/form/form-groups/form-input/input-wrapper/InputWrapper.tsx +++ b/src-ts/lib/form/form-groups/form-input/input-wrapper/InputWrapper.tsx @@ -28,6 +28,7 @@ const InputWrapper: ForwardRefExoticComponent = forwardRef = forwardRef diff --git a/src-ts/tools/learn/welcome/WelcomePage.module.scss b/src-ts/tools/learn/welcome/WelcomePage.module.scss index 97ee4655c..deaf82325 100644 --- a/src-ts/tools/learn/welcome/WelcomePage.module.scss +++ b/src-ts/tools/learn/welcome/WelcomePage.module.scss @@ -57,6 +57,12 @@ align-items: center; gap: $space-sm; } + + @include ltemd { + flex-direction: column; + align-items: flex-start; + gap: $space-xxl; + } } .badge { @@ -77,4 +83,22 @@ > * { min-width: 326px; } + + > :global(.input-wrapper) { + width: 100%; + + > :global(.input-el) { + margin: 0; + } + } + + @include ltelg { + flex-direction: column; + align-items: flex-start; + gap: $space-lg; + } + + @include ltemd { + width: 100%; + } } From 8a59c3f82fb9822cb6fcc8d358ff5ca0bbfe3a68 Mon Sep 17 00:00:00 2001 From: Vasilica Olariu Date: Tue, 20 Sep 2022 12:03:09 +0300 Subject: [PATCH 7/7] TCA-441 - update button style for secondary buttons on learn landing page --- src-ts/tools/learn/welcome/courses-card/CoursesCard.tsx | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src-ts/tools/learn/welcome/courses-card/CoursesCard.tsx b/src-ts/tools/learn/welcome/courses-card/CoursesCard.tsx index 29c461d0a..2ceaabb38 100644 --- a/src-ts/tools/learn/welcome/courses-card/CoursesCard.tsx +++ b/src-ts/tools/learn/welcome/courses-card/CoursesCard.tsx @@ -1,7 +1,7 @@ import classNames from 'classnames' import { Dispatch, FC, SetStateAction, useEffect, useState } from 'react' -import { Button } from '../../../../lib' +import { Button, ButtonStyle } from '../../../../lib' import { CourseTitle, LearnCertification, @@ -20,6 +20,8 @@ interface CoursesCardProps { const CoursesCard: FC = (props: CoursesCardProps) => { + const [buttonStyle, setButtonStyle]: [ButtonStyle, Dispatch>] + = useState('primary') const [buttonLabel, setButtonLabel]: [string, Dispatch>] = useState('') const [link, setLink]: [string, Dispatch>] @@ -42,6 +44,7 @@ const CoursesCard: FC = (props: CoursesCardProps) => { if (isCompleted) { // if the course is completed, View the Certificate + setButtonStyle('secondary') setButtonLabel('View Certificate') setLink(getCertificatePath( props.certification.providerName, @@ -58,6 +61,7 @@ const CoursesCard: FC = (props: CoursesCardProps) => { } else { // otherwise this course is in-progress, so Resume the course at the next lesson + setButtonStyle('secondary') setButtonLabel('Resume') setLink(getLessonPathFromCurrentLesson( props.certification.providerName, @@ -85,7 +89,7 @@ const CoursesCard: FC = (props: CoursesCardProps) => {
{!!link && (