diff --git a/public/locales/en/translation.json b/public/locales/en/translation.json index 133ea55..56f2981 100755 --- a/public/locales/en/translation.json +++ b/public/locales/en/translation.json @@ -1,7 +1,7 @@ { "serv": { - "goHome": "Home", - "noSuchPath": "We don't have this page" + "goHome": "Home page", + "noSuchPath": "We don't have such a page" }, "sidemenu": { "dashboard": "Dashboard", @@ -19,45 +19,110 @@ "hellousr": "Hello, {{username}}", "edit": "Edit", "language": "Language", - "selectLanguage": "Select language", + "selectLanguage": "Select a language", "save": "Save", "cancel": "Cancel", "account": { - "info": "Personal information", + "info": "Personal Information", "mail": "Mail", - "connect": "Add account", - "connectedAccounts_one": "Connected account", - "connectedAccounts_other": "Connected accounts", - "settings": "Account settings" + "connect": "Add Account", + "connectedAccounts_one": "Linked Account", + "connectedAccounts_other": "Linked Accounts", + "settings": "Account Settings" }, "security": { "password": { "caption": "Password", - "twoFactor": "Two factor authentication (2FA)", - "description": "Keep your account secure by enabling 2FA via SMS or using a temporary one-time passcode (TOTP) from an authenticator app." + "twoFactor": "Two-factor authentication (2FA)", + "description": "Protect your account by enabling 2FA via SMS or using a temporary one-time password (OTP) from the authentication app." }, "activity": { "caption": "Device activity" } }, "search": { - "label": "Search for something.." + "label": "We will find something.." }, "subscriptions": { - "subscribed": "Service have been connected" + "subscribed": "The service is attached to the account" }, "viewHistory": "View history", - "logOutEverywhere": "log out from all devices", + "logOutEverywhere": "Log out from all devices", "back": "Back", - "logOut": "Log out", + "logOut": "Exit", "failures": { "subscriptions": { - "failure": "Failed to connect service", - "exists": "Service have already been connected", - "confirmation": "Invalid confirmation information provided" + "failure": "Failed to attach the service to your account", + "exists": "The service was already attached to your account earlier", + "confirmation": "Invalid password" }, "services": { - "fork": "Failed to authenticate in service" + "fork": "Failed to perform authorization in the service" } + }, + "articlePage": { + "abstract": "Abstract", + "keywords": "Keywords" + }, + "navbar": { + "createNew": "Create an article", + "about": { + "navTitle": "About the project", + "aboutProject": "About Scipaper", + "contacts": "Contacts", + "help": "Help" + }, + "library": { + "navTitle": "My library", + "publications": "Publications", + "favorites": "Favorites", + "collections": "Collections", + "recentViewed": "History" + }, + "auth": { + "signIn": "Sign In", + "signUp": "Sign Up" + } + }, + "footer": { + "accountSettings": "Account Settings", + "about": "About Scipaper", + "help": "Help", + "contactUs": "Contacts", + "allRightsReserved": "All rights reserved", + "termsOfUse": "Terms of Use", + "privacyPolicy": "Privacy Policy", + "coockiesPolicy": "coockies Usage Policy", + "supportedBy": "Created" + }, + "mainPage": { + "title": "Scientific Library with free access", + "search": "Search", + "article_one": "Articles", + "article_few": "Articles", + "article_many": "Articles", + "advancedSearch": "Advanced search", + "featuredArticles": { + "title": "Popular articles", + "descriptionPart1": "Choose the one you are interested in", + "descriptionPart2": "Scientific category", + "categories": { + "Medical": "Medical", + "TechnicsAndTechlonogies": "Technics and Technology", + "Fundamental": "Fundamental", + "Humanitarian": "Humanitarian", + "Agricultural": "Agricultural", + "Social": "Social" + } + }, + "featuredAuthors": "Popular authors", + "more": "More", + "showAll": "Show all" + }, + "searchResults": { + "title": "Search results", + "totalResults":"Total results", + "nothingFound": "Nothing found" } -} \ No newline at end of file + +} diff --git a/src/components/Badge.tsx b/src/components/Badge.tsx index 34974eb..75afd11 100755 --- a/src/components/Badge.tsx +++ b/src/components/Badge.tsx @@ -5,6 +5,7 @@ import classNames from "classnames"; import { StyleType } from "core/_variants"; import React from "react"; +import {ReactComponent as DeleteIcon} from "./Filters/svg/chest.svg" /* -------------------------------------------------------------------------- */ /* Component props */ @@ -15,7 +16,8 @@ type Props = { children: React.ReactNode; className?: string; iconed?: boolean; - onClick?: () => void; + onClick?: (element: any) => void; + closeOption?: Boolean; } & Omit, "">; /* -------------------------------------------------------------------------- */ @@ -27,17 +29,18 @@ function Badge({ children, onClick, emphasis = "low", + closeOption = false, ...props }: Props): JSX.Element { return ( + <> {children} + {closeOption && ( + + )} + ); } export default Badge; diff --git a/src/components/Checkbox.tsx b/src/components/Checkbox.tsx index d13b86b..913ceb0 100755 --- a/src/components/Checkbox.tsx +++ b/src/components/Checkbox.tsx @@ -1,12 +1,12 @@ import React from "react"; import classNames from "classnames"; -import { ReactComponent as Checkmark } from "assets/svg/check.svg"; +import { ReactComponent as Checkmark } from "../assets/svg/arrow-down.svg"; export type Props = { /** * Control the state of checkbox */ - isChecked: boolean; + isChecked?: boolean; /** * An Element next to the checkbox */ @@ -33,12 +33,8 @@ const Checkbox = ({ children, className, isChecked, ...props }: Props) => { checked={isChecked} {...props} /> -
- +
+
{children} diff --git a/src/components/Disclosure.tsx b/src/components/Disclosure.tsx new file mode 100644 index 0000000..e17f290 --- /dev/null +++ b/src/components/Disclosure.tsx @@ -0,0 +1,55 @@ +/* -------------------------------------------------------------------------- */ +/* Imports */ +/* -------------------------------------------------------------------------- */ +import React from "react"; +import { Disclosure as Disclose } from "@headlessui/react"; +import { ReactComponent as SelectIcon } from "../assets/svg/arrow-down.svg"; +import classNames from "classnames"; +/* -------------------------------------------------------------------------- */ +/* Component props */ +/* -------------------------------------------------------------------------- */ +type Props = { + children?: React.ReactNode; + className?: string; + caption?: string; +}; +/* -------------------------------------------------------------------------- */ +/* styles */ +/* -------------------------------------------------------------------------- */ +const ButtonStyle = ` +flex w-full +justify-between +items-center +py-2 text-left +text-base +font-medium +text-black-400 +`; +/* -------------------------------------------------------------------------- */ +/* Component implementation */ +/* -------------------------------------------------------------------------- */ +export default function Disclosure({ children, className, caption }: Props) { + return ( +
+
+ + {({ open }) => ( + <> + + {caption} + + + + {children} + + + )} + +
+
+ ); +} diff --git a/src/components/Filters/AppiledFilters.tsx b/src/components/Filters/AppiledFilters.tsx new file mode 100644 index 0000000..570382f --- /dev/null +++ b/src/components/Filters/AppiledFilters.tsx @@ -0,0 +1,64 @@ +/* -------------------------------------------------------------------------- */ +/* Imports */ +/* -------------------------------------------------------------------------- */ +import React from "react"; +import Badge from "components/Badge"; +import { IProduct } from "./IProdutct"; +import classNames from "classnames"; +/* -------------------------------------------------------------------------- */ +/* Component props */ +/* -------------------------------------------------------------------------- */ +type Props = { + hints?: IProduct[]; + delFilter: (element: IProduct) => void; + clearAll: () => void; + className?: string; +}; +/* -------------------------------------------------------------------------- */ +/* Component implementation */ +/* -------------------------------------------------------------------------- */ +export default function AppiledFilters({ + hints, + delFilter, + clearAll, + className, +}: Props) { + return ( + <> +
+
+
Фильтры
+
+ +
+
+ +
+ + ); +} diff --git a/src/components/Filters/Filter.tsx b/src/components/Filters/Filter.tsx new file mode 100644 index 0000000..384b37d --- /dev/null +++ b/src/components/Filters/Filter.tsx @@ -0,0 +1,109 @@ +import React from "react"; +import Disclosure from "components/Disclosure"; +import AppiledFilters from "./AppiledFilters"; +import SearchFilterBar from "./SearchFilterBar"; +import axios from "axios"; +import { useDebounce } from "./functions/debounce"; +import { IProduct } from "./IProdutct"; + +type Props = { + className?: string; +}; + +export default function Fiter({ className }: Props) { + const [checkedId, setCheckedId] = React.useState>([]); + const [activeFilter, setActiveFilter] = React.useState>([]); + + /* -------------------------------------------------------------------------- */ + /* Test request to demonstrate the basic functionality of the filter */ + /* -------------------------------------------------------------------------- */ + + const [query, setQuery] = React.useState(""); + const [hints, setHints] = React.useState>([]); + + const onChange = (e: React.ChangeEvent) => { + setQuery(e.target.value); + }; + const debounced = useDebounce(query); + + async function fetchProducts() { + const response = await axios.get( + `https://dummyjson.com/products/search?q=${debounced}` + ); + setHints(response.data.products); + } + React.useEffect(() => { + fetchProducts(); + }, [debounced, query]); + + const isChecked = (item: number) => (checkedId.includes(item) ? true : false); + + const handleChange = (e: React.ChangeEvent) => { + const selectedId = parseInt(e.target.value); + + /* ------------- Check if "checkedId" contains "selectedIds" ------------- */ + /* -------------- If true, this checkbox is already checked -------------- */ + + if (checkedId.includes(selectedId)) { + setCheckedId(checkedId.filter((id) => id !== selectedId)); + setActiveFilter(activeFilter.filter((item) => item.id !== selectedId)); + } else { + const newId = [...checkedId]; + newId.push(selectedId); + setCheckedId(newId); + + /* --------------- Adding checked filters to AppliedFilters component -------------- */ + + hints.forEach((hint) => { + if (hint.id === selectedId) + activeFilter.push({ id: selectedId, brand: hint.brand }); + setActiveFilter(activeFilter); + }); + } + }; + + /* ------------- Delete filter from AppliedFilters component ------------- */ + + const DelFilter = (e: IProduct) => { + setActiveFilter(activeFilter.filter((item) => item.id !== e.id)); + setCheckedId(checkedId.filter((id) => id !== e.id)); + }; + const clearAll = () => { + setActiveFilter([]); + setCheckedId([]); + setQuery(""); + }; + //console.log(activeFilter) + return ( + <> +
+
+ + + + + +

контент...

+
+ +

контент...

+
+ +

контент...

+
+
+
+ + ); +} diff --git a/src/components/Filters/IProdutct.ts b/src/components/Filters/IProdutct.ts new file mode 100644 index 0000000..9101267 --- /dev/null +++ b/src/components/Filters/IProdutct.ts @@ -0,0 +1,23 @@ + export interface IProduct { + id?: number; + title?: string; + description?: string; + price?: number; + discountPercentage?: number; + rating?: number; + stock?: number; + brand?: string; + category?: string; + thumbnail?: string; + images?: string[]; + } + + export interface serverResponse { + products: T[]; + total: number; + skip: number; + limit: number; + } + + + diff --git a/src/components/Filters/SearchFilterBar.tsx b/src/components/Filters/SearchFilterBar.tsx new file mode 100644 index 0000000..f9e6362 --- /dev/null +++ b/src/components/Filters/SearchFilterBar.tsx @@ -0,0 +1,117 @@ +import { useState, useCallback, Fragment } from "react"; +import { Combobox, Transition } from "@headlessui/react"; +import { IProduct } from "./IProdutct"; +import Checkbox from "components/Checkbox"; +import { ReactComponent as SearchIcon } from "./svg/searchIcon.svg"; +import classNames from "classnames"; + +type Props = { + hints: IProduct[]; + handleChange: (e: React.ChangeEvent) => void; + isChecked: (item: number) => boolean; + className?: string; + onChange: (e: React.ChangeEvent) => void; + placeHolder: string; + query: string; +}; + +export default function SearchFilterBar({ + hints, + isChecked, + handleChange, + className, + onChange, + placeHolder, + query, +}: Props): JSX.Element { + const [checkList, setCheckList] = useState(hints); + const [showFilters, SetShowFilters] = useState(false); + + const ShowAllFilters = useCallback(() => { + SetShowFilters((prev) => !prev); + }, [SetShowFilters]); + + return ( + <> + + {({ open }) => ( +
+
+
+ +
+ +
+
+
+
+ +
+
+
+ + + {open && ( +
    +
  • + {hints.length >= 5 && query !== "" && ( + + )} +
  • + {hints.length > 0 ? ( + hints.map((item) => ( +
  • + +

    {item.brand}

    +
    +
  • + )) + ) : ( +

    Nothing Found

    + )} +
+ )} +
+
+
+
+ )} +
+ + ); +} diff --git a/src/components/Filters/functions/debounce.ts b/src/components/Filters/functions/debounce.ts new file mode 100644 index 0000000..7d7bc78 --- /dev/null +++ b/src/components/Filters/functions/debounce.ts @@ -0,0 +1,11 @@ +import React from "react"; + +export function useDebounce(value: string, delay: number = 300) { + const [debounced, setDebouced] = React.useState(value); + React.useEffect(() => { + const handler = setTimeout(() => setDebouced(value), delay); + return () => clearTimeout(handler); + }, [value, delay]); + + return debounced; +} diff --git a/src/components/Filters/svg/chest.svg b/src/components/Filters/svg/chest.svg new file mode 100644 index 0000000..3b1d9ff --- /dev/null +++ b/src/components/Filters/svg/chest.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/components/Filters/svg/searchIcon.svg b/src/components/Filters/svg/searchIcon.svg new file mode 100644 index 0000000..dba33fe --- /dev/null +++ b/src/components/Filters/svg/searchIcon.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/components/MainPage/sections/FeaturedArticlesCards.tsx b/src/components/MainPage/sections/FeaturedArticlesCards.tsx index bfb2a07..78f0289 100755 --- a/src/components/MainPage/sections/FeaturedArticlesCards.tsx +++ b/src/components/MainPage/sections/FeaturedArticlesCards.tsx @@ -98,10 +98,10 @@ const FeaturedArticlesCards = () => { const navigationPrevRef = useRef(null); const navigationNextRef = useRef(null); const paginationRef = useRef(null); - const [t, i18next] = useTranslation() + const [t, i18next] = useTranslation(); return ( -
+
- + {t("mainPage.more")} diff --git a/src/components/MainPage/sections/FeaturedAuthorsCards.tsx b/src/components/MainPage/sections/FeaturedAuthorsCards.tsx index 965a243..87fff9c 100755 --- a/src/components/MainPage/sections/FeaturedAuthorsCards.tsx +++ b/src/components/MainPage/sections/FeaturedAuthorsCards.tsx @@ -75,10 +75,12 @@ export default function FeaturedAuthorsCards(): JSX.Element { return (
{/* The Title of Featured Authors section */} - {t("mainPage.featuredAuthors")} + + {t("mainPage.featuredAuthors")} + {/* Featured Authors section */} -
+
- {t('mainPage.more')} + {t("mainPage.more")} diff --git a/src/components/fetchAnArticle/AnArticle.tsx b/src/components/fetchAnArticle/AnArticle.tsx index 11c8d26..73a8f91 100644 --- a/src/components/fetchAnArticle/AnArticle.tsx +++ b/src/components/fetchAnArticle/AnArticle.tsx @@ -33,7 +33,7 @@ const AnArticle = () => { ) : ( <> - + {article?.topic}
@@ -52,25 +52,18 @@ const AnArticle = () => { /> {article?.tags && (
- - {t('articlePage.keywords')} + + {t("articlePage.keywords")} - {article?.tags}
)}
- - {t('articlePage.abstract')} + + {t("articlePage.abstract")} {article?.summary !== undefined ? ( diff --git a/src/components/fetchAnArticle/AnArticleBody.tsx b/src/components/fetchAnArticle/AnArticleBody.tsx index 55d7f95..0329981 100644 --- a/src/components/fetchAnArticle/AnArticleBody.tsx +++ b/src/components/fetchAnArticle/AnArticleBody.tsx @@ -27,7 +27,7 @@ const AnArticleBody = () => { {shouldShowLoading ? ( <> - +
{ ) : ( <> - + {article?.topic}
diff --git a/src/components/fetchAnArticle/AskeletonArticle.tsx b/src/components/fetchAnArticle/AskeletonArticle.tsx index f0b9702..d8bdcfc 100644 --- a/src/components/fetchAnArticle/AskeletonArticle.tsx +++ b/src/components/fetchAnArticle/AskeletonArticle.tsx @@ -3,7 +3,7 @@ import "react-loading-skeleton/dist/skeleton.css"; const AskeletonArticle = () => { return ( <> - +
diff --git a/src/components/typography/Link.tsx b/src/components/typography/Link.tsx index 27c073f..f370d4d 100755 --- a/src/components/typography/Link.tsx +++ b/src/components/typography/Link.tsx @@ -51,6 +51,7 @@ export default function Link({ : style } aria-disabled={disabled} + className="flex items-center" {...props} > {children} diff --git a/src/index.tsx b/src/index.tsx index 7e200b3..9f61062 100755 --- a/src/index.tsx +++ b/src/index.tsx @@ -42,7 +42,8 @@ root.render( } /> - }> + } /> + }> diff --git a/src/pages/SearchResultsPage.tsx b/src/pages/SearchResultsPage.tsx index cca667b..e6d3f35 100644 --- a/src/pages/SearchResultsPage.tsx +++ b/src/pages/SearchResultsPage.tsx @@ -6,6 +6,7 @@ import { SearchResultSection } from "components/SearchResultsSection"; import { useSearchStoreImplementation } from "searchResults/data/searchStoreImplementation"; import { useSearchViewModel } from "searchResults/controller/searchResultsViewModel"; import { Loader } from "components/Loader/Loader"; +import Fiter from "components/Filters/Filter"; export const SearchResultsPage = () => { return (