diff --git a/frontend/src/assets/CircleClear.svg b/frontend/src/assets/CircleClear.svg new file mode 100644 index 0000000..0269af0 --- /dev/null +++ b/frontend/src/assets/CircleClear.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/assets/DropdownArrow.svg b/frontend/src/assets/DropdownArrow.svg new file mode 100644 index 0000000..f5f4448 --- /dev/null +++ b/frontend/src/assets/DropdownArrow.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/src/assets/ExternalLink.svg b/frontend/src/assets/ExternalLink.svg new file mode 100644 index 0000000..65ac377 --- /dev/null +++ b/frontend/src/assets/ExternalLink.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/src/assets/Filter.svg b/frontend/src/assets/Filter.svg new file mode 100644 index 0000000..805fa52 --- /dev/null +++ b/frontend/src/assets/Filter.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/assets/Search.svg b/frontend/src/assets/Search.svg new file mode 100644 index 0000000..d434fd6 --- /dev/null +++ b/frontend/src/assets/Search.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/src/assets/socials/Medium.svg b/frontend/src/assets/socials/Medium.svg new file mode 100644 index 0000000..591c00d --- /dev/null +++ b/frontend/src/assets/socials/Medium.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/src/assets/socials/Telegram.svg b/frontend/src/assets/socials/Telegram.svg new file mode 100644 index 0000000..49c2957 --- /dev/null +++ b/frontend/src/assets/socials/Telegram.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/src/assets/socials/Twitter.svg b/frontend/src/assets/socials/Twitter.svg new file mode 100644 index 0000000..e6329a3 --- /dev/null +++ b/frontend/src/assets/socials/Twitter.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/frontend/src/components/checkbox/checkbox.module.scss b/frontend/src/components/checkbox/checkbox.module.scss new file mode 100644 index 0000000..9b544e1 --- /dev/null +++ b/frontend/src/components/checkbox/checkbox.module.scss @@ -0,0 +1,58 @@ +// @import 'scss/font_mixins'; + +.checkbox { + display: inline-flex; + justify-content: center; + align-items: center; + + cursor: pointer; + gap: 8px; + user-select: none; + + &.checked .indicator { + opacity: 1; + } + + &.disabled { + cursor: not-allowed; + opacity: 0.5; + } + + .text { + flex: 1; + // @include Small; + color: var(--token-dark-500); + } + + &.checked { + .track { + border: solid 1px var(--token-primary-400); + } + + .text { + color: var(--token-light-white); + transition: color 100ms; + } + } +} + +.track { + display: flex; + justify-content: center; + align-items: center; + + border: solid 1px var(--token-dark-500); + border-radius: 6px; + width: 18px; + height: 18px; +} + +.indicator { + background: var(--token-primary-500); + border-radius: 2px; + width: 8px; + height: 8px; + + opacity: 0; + transition: opacity 100ms; +} diff --git a/frontend/src/components/checkbox/checkbox.tsx b/frontend/src/components/checkbox/checkbox.tsx new file mode 100644 index 0000000..9ed730b --- /dev/null +++ b/frontend/src/components/checkbox/checkbox.tsx @@ -0,0 +1,30 @@ +import { ForwardedRef, forwardRef, InputHTMLAttributes } from 'react'; +import classNames from 'classnames/bind'; +import styles from './checkbox.module.scss'; + +const cx = classNames.bind(styles); + +export interface CheckboxProps extends InputHTMLAttributes { + label: string + checked?: boolean +} + +const Checkbox = forwardRef( + ( + { className, label, checked, ...attrs }: CheckboxProps, + ref: ForwardedRef + ) => { + const { disabled } = attrs; + return ( + + ); + }, +); + +export default Checkbox; diff --git a/frontend/src/components/filters/dropdowns/FilterDropdown.module.scss b/frontend/src/components/filters/dropdowns/FilterDropdown.module.scss new file mode 100644 index 0000000..f7d8ef2 --- /dev/null +++ b/frontend/src/components/filters/dropdowns/FilterDropdown.module.scss @@ -0,0 +1,123 @@ +.filter__row { + display: flex; + gap: 24px; + align-items: center; + margin-bottom: 24px; +} + +.filter__dropdown__container { + max-width: 200px; + width: 100%; + display: flex; + gap: 24px; + position: relative; + + .selector__wrapper { + display: flex; + align-items: center; + gap: 4px; + width: 100%; + + .selector { + padding: 8px 12px; + background: var(--token-light-500); + border: 1px solid var(--token-light-800); + border-radius: 8px; + overflow: hidden; + display: flex; + justify-content: space-between; + align-items: center; + gap: 8px; + appearance: none; + width: 100%; + + .selected__wrapper { + display: flex; + align-items: center; + gap: 8px; + } + + span { + display: flex; + font-size: 14px; + font-weight: 500; + color: var(--token-dark-500); + } + + &.open { + background-color: var(--token-light-800); + border-color: var(--token-dark-700); + } + + &:hover { + background-color: var(--token-light-800); + border-color: var(--token-dark-700); + transition: all .25s; + } + } + + .clear__wrapper { + display: flex; + align-items: center; + } + } + + .options { + padding: 8px 2px 8px 12px; + position: absolute; + width: 100%; + background: var(--token-light-800); + border: 1px solid var(--token-dark-700); + + border-radius: 8px; + z-index: 10; + max-height: 200px; + box-shadow: 0px 5px 7px 0px #0000009c; + top: 40px; + overflow: hidden; + + .options__container { + padding-right: 10px; + padding-bottom: 8px; + overflow: auto; + max-height: 190px; + display: flex; + flex-direction: column; + gap: 4px; + + &::-webkit-scrollbar { + width: 12px; + display: block; + } + + &::-webkit-scrollbar-thumb { + border-radius: 16px; + background-color: hsl(0, 0%, 47%); + border: 2px solid var(--token-light-800); + } + + &::-webkit-scrollbar-track { + background-color: transparent; + } + } + } +} + +@media (max-width: 590px) { + .filter__row { + // flex-direction: column; + flex-wrap: wrap; + align-items: flex-start; + gap: 4px; + } + + .filter__dropdown__container { + .selector__wrapper { + .selector { + span { + font-size: 12px; + } + } + } + } +} \ No newline at end of file diff --git a/frontend/src/components/filters/dropdowns/InhabitantFilter.tsx b/frontend/src/components/filters/dropdowns/InhabitantFilter.tsx new file mode 100644 index 0000000..e8b895f --- /dev/null +++ b/frontend/src/components/filters/dropdowns/InhabitantFilter.tsx @@ -0,0 +1,119 @@ +import { useEffect, useRef, useState } from 'react'; +import classNames from 'classnames/bind'; +import { inhabitantOptions } from '../options'; +import { GalleryFiltersProps } from 'pages/nft/NFTs'; +import { ReactComponent as DropdownArrowIcon } from "assets/DropdownArrow.svg"; +import { ReactComponent as CircleClearIcon } from "assets/CircleClear.svg"; +import Checkbox from 'components/checkbox/checkbox'; +import styles from './FilterDropdown.module.scss'; + +const cx = classNames.bind(styles); + +export const InhabitantFilter = ({ + galleryFilters, + setGalleryFilters, +}: { + galleryFilters: GalleryFiltersProps + setGalleryFilters: ({ + planetNumber, + planetNames, + planetInhabitants, + nftObjects + }: GalleryFiltersProps) => void, +}) => { + const [open, setOpen] = useState(false) + const ref = useRef(null) + + useEffect(() => { + document.addEventListener("mousedown", handleClickOutside) + return () => { + document.removeEventListener("mousedown", handleClickOutside) + } + }, []) + + const handleClickOutside = (event: MouseEvent) => { + if (ref.current && !ref.current.contains(event.target as Node)) { + setOpen(false) + } + } + + const [selectedInhabitants, setSelectedInhabitants] = useState(galleryFilters.planetInhabitants || []); + const inhabitantDropdownValue = + selectedInhabitants.length ? + selectedInhabitants.length === 1 ? + selectedInhabitants[0] : `${selectedInhabitants.length} inhabitant selected` + : "Select an inhabitant"; + + useEffect(() => { + setGalleryFilters({ + ...galleryFilters, + planetInhabitants: selectedInhabitants, + }); + }, [selectedInhabitants]); + + const handleInhabitantClick = (inhabitantsName: string) => { + if (selectedInhabitants?.includes(inhabitantsName)) { + setSelectedInhabitants(selectedInhabitants.filter(name => name !== inhabitantsName)); + } else { + setSelectedInhabitants(selectedInhabitants ? [...selectedInhabitants, inhabitantsName] : [inhabitantsName]); + } + } + + const clearInhabitantFilters = () => { + setGalleryFilters({ + ...galleryFilters, + planetInhabitants: [], + }); + setSelectedInhabitants([]); + } + + return ( +
+
+ + {selectedInhabitants.length > 0 && ( +
+ +
+ )} +
+ {open && ( +
+
+ {inhabitantOptions.map(inhabitant => ( + handleInhabitantClick(inhabitant)} + /> + ))} +
+
+ )} +
+ ) +} diff --git a/frontend/src/components/filters/dropdowns/ObjectFilter.tsx b/frontend/src/components/filters/dropdowns/ObjectFilter.tsx new file mode 100644 index 0000000..3252701 --- /dev/null +++ b/frontend/src/components/filters/dropdowns/ObjectFilter.tsx @@ -0,0 +1,119 @@ +import { useEffect, useRef, useState } from 'react'; +import classNames from 'classnames/bind'; +import { objectOptions } from '../options'; +import { GalleryFiltersProps } from 'pages/nft/NFTs'; +import { ReactComponent as DropdownArrowIcon } from "assets/DropdownArrow.svg"; +import { ReactComponent as CircleClearIcon } from "assets/CircleClear.svg"; +import Checkbox from 'components/checkbox/checkbox'; +import styles from './FilterDropdown.module.scss'; + +const cx = classNames.bind(styles); + +export const ObjectFilter = ({ + galleryFilters, + setGalleryFilters, +}: { + galleryFilters: GalleryFiltersProps + setGalleryFilters: ({ + planetNumber, + planetNames, + planetInhabitants, + nftObjects + }: GalleryFiltersProps) => void, +}) => { + const [open, setOpen] = useState(false) + const ref = useRef(null) + + useEffect(() => { + document.addEventListener("mousedown", handleClickOutside) + return () => { + document.removeEventListener("mousedown", handleClickOutside) + } + }, []) + + const handleClickOutside = (event: MouseEvent) => { + if (ref.current && !ref.current.contains(event.target as Node)) { + setOpen(false) + } + } + + const [selectedObjects, setSelectedObjects] = useState(galleryFilters.nftObjects || []); + const objectDropdownValue = + selectedObjects.length ? + selectedObjects.length === 1 ? + selectedObjects[0] : `${selectedObjects.length} objects selected` + : "Select an object"; + + useEffect(() => { + setGalleryFilters({ + ...galleryFilters, + nftObjects: selectedObjects, + }); + }, [selectedObjects]); + + const handleObjectClick = (objectName: string) => { + if (selectedObjects?.includes(objectName)) { + setSelectedObjects(selectedObjects.filter(name => name !== objectName)); + } else { + setSelectedObjects(selectedObjects ? [...selectedObjects, objectName] : [objectName]); + } + } + + const clearObjectFilters = () => { + setGalleryFilters({ + ...galleryFilters, + nftObjects: [], + }); + setSelectedObjects([]); + } + + return ( +
+
+ + {selectedObjects.length > 0 && ( +
+ +
+ )} +
+ {open && ( +
+
+ {objectOptions.map(nftObject => ( + handleObjectClick(nftObject)} + /> + ))} +
+
+ )} +
+ ) +} diff --git a/frontend/src/components/filters/dropdowns/PlanetFilter.tsx b/frontend/src/components/filters/dropdowns/PlanetFilter.tsx new file mode 100644 index 0000000..2305644 --- /dev/null +++ b/frontend/src/components/filters/dropdowns/PlanetFilter.tsx @@ -0,0 +1,119 @@ +import { useEffect, useRef, useState } from 'react'; +import classNames from 'classnames/bind'; +import { planetOptions } from '../options'; +import { GalleryFiltersProps } from 'pages/nft/NFTs'; +import { ReactComponent as DropdownArrowIcon } from "assets/DropdownArrow.svg"; +import { ReactComponent as CircleClearIcon } from "assets/CircleClear.svg"; +import Checkbox from 'components/checkbox/checkbox'; +import styles from './FilterDropdown.module.scss'; + +const cx = classNames.bind(styles); + +export const PlanetFilter = ({ + galleryFilters, + setGalleryFilters, +}: { + galleryFilters: GalleryFiltersProps + setGalleryFilters: ({ + planetNumber, + planetNames, + planetInhabitants, + nftObjects + }: GalleryFiltersProps) => void, +}) => { + const [open, setOpen] = useState(false) + const ref = useRef(null) + + useEffect(() => { + document.addEventListener("mousedown", handleClickOutside) + return () => { + document.removeEventListener("mousedown", handleClickOutside) + } + }, []) + + const handleClickOutside = (event: MouseEvent) => { + if (ref.current && !ref.current.contains(event.target as Node)) { + setOpen(false) + } + } + + const [selectedPlanets, setSelectedPlanets] = useState(galleryFilters.planetNames || []); + const planetDropdownValue = + selectedPlanets.length ? + selectedPlanets.length === 1 ? + selectedPlanets[0] : `${selectedPlanets.length} planets selected` + : "Select a planet"; + + useEffect(() => { + setGalleryFilters({ + ...galleryFilters, + planetNames: selectedPlanets, + }); + }, [selectedPlanets]); + + const handlePlanetClick = (planetName: string) => { + if (selectedPlanets?.includes(planetName)) { + setSelectedPlanets(selectedPlanets.filter(name => name !== planetName)); + } else { + setSelectedPlanets(selectedPlanets ? [...selectedPlanets, planetName] : [planetName]); + } + } + + const clearPlanetFilters = () => { + setGalleryFilters({ + ...galleryFilters, + planetNames: [], + }); + setSelectedPlanets([]); + } + + return ( +
+
+ + {selectedPlanets.length > 0 && ( +
+ +
+ )} +
+ {open && ( +
+
+ {planetOptions.map(planet => ( + handlePlanetClick(planet)} + /> + ))} +
+
+ )} +
+ ) +} diff --git a/frontend/src/components/filters/dropdowns/index.tsx b/frontend/src/components/filters/dropdowns/index.tsx new file mode 100644 index 0000000..dd3869d --- /dev/null +++ b/frontend/src/components/filters/dropdowns/index.tsx @@ -0,0 +1,36 @@ +import { PlanetFilter } from './PlanetFilter'; +import { InhabitantFilter } from './InhabitantFilter'; +import { ObjectFilter } from './ObjectFilter'; +import { GalleryFiltersProps } from 'pages/nft/NFTs'; + +import styles from './FilterDropdown.module.scss'; + +export const FilterDropdowns = ({ + galleryFilters, + setGalleryFilters, +}: { + galleryFilters: GalleryFiltersProps + setGalleryFilters: ({ + planetNumber, + planetNames, + planetInhabitants, + nftObjects + }: GalleryFiltersProps) => void, +}) => { + return ( +
+ + + +
+ ); +}; diff --git a/frontend/src/components/filters/facet.module.scss b/frontend/src/components/filters/facet.module.scss new file mode 100644 index 0000000..072ba59 --- /dev/null +++ b/frontend/src/components/filters/facet.module.scss @@ -0,0 +1,95 @@ +.facet { + display: flex; + flex-direction: column; + gap: 24px; + width: 20%; + border-right: 1px solid #E5E5E5; + padding: 0px 8px 0px 24px; + + position: fixed; + left: -100%; + width: 20%; + display: flex; + justify-content: space-between; + flex-direction: column; + z-index: 100; + + &.open { + left: 0; + transition: left 0.5s cubic-bezier(0.22, 1, 0.36, 1); + } + + &.closed { + left: -100%; + transition: left 2s cubic-bezier(0.22, 1, 0.36, 1); + } + + .facet__header { + display: flex; + justify-content: space-between; + + .facet__title { + h3 { + color: var(--token-dark-500); + font-size: 16px; + font-weight: 600; + } + } + + .facet__clear { + .clear__button { + font-size: 12px; + font-weight: 600; + color: #797979; + margin-right: 12px; + + &:hover { + color: #000; + } + } + } + } + + .facet__body { + display: flex; + flex-direction: column; + gap: 24px; + + .filter__section { + display: flex; + flex-direction: column; + gap: 4px; + + .filter__section__header { + h4 { + font-size: 16px; + font-weight: 600; + color: var(--token-dark-500); + } + } + + .filter__section__body { + max-height: 120px; + overflow: auto; + } + } + + .facet__item { + display: flex; + flex-direction: row; + align-items: center; + gap: 8px; + + .facet__item__checkbox { + input { + + } + } + .facet__item__name { + span { + + } + } + } + } +} \ No newline at end of file diff --git a/frontend/src/components/filters/facet.tsx b/frontend/src/components/filters/facet.tsx new file mode 100644 index 0000000..99b0ae6 --- /dev/null +++ b/frontend/src/components/filters/facet.tsx @@ -0,0 +1,150 @@ +/* eslint-disable react-hooks/exhaustive-deps */ + +import { useEffect, useState } from 'react'; +import styles from './facet.module.scss'; +import { planetOptions, inhabitantOptions, objectOptions } from './options'; +import { GalleryFiltersProps } from 'pages/nft/NFTs'; + +export const Facet = ({ + galleryFilters, + setGalleryFilters, + displayFacet, +}: { + galleryFilters: GalleryFiltersProps + setGalleryFilters: ({ + planetNumber, + planetNames, + planetInhabitants, + nftObjects + }: GalleryFiltersProps) => void, + displayFacet: boolean, +}) => { + const [selectedPlanets, setSelectedPlanets] = useState([]); + const [selectedInhabitants, setSelectedInhabitants] = useState([]); + const [selectedObjects, setSelectedObjects] = useState([]); + + useEffect(() => { + setGalleryFilters({ + ...galleryFilters, + planetNames: selectedPlanets, + planetInhabitants: selectedInhabitants, + nftObjects: selectedObjects, + }); + }, [selectedPlanets, selectedInhabitants, selectedObjects]); + + const clearFilters = () => { + setGalleryFilters({ + planetNumber: null, + planetNames: [], + planetInhabitants: [], + nftObjects: [], + }); + setSelectedPlanets([]); + setSelectedInhabitants([]); + setSelectedObjects([]); + }; + + const handlePlanetClick = (planetName: string) => { + if (selectedPlanets?.includes(planetName)) { + setSelectedPlanets(selectedPlanets.filter(name => name !== planetName)); + } else { + setSelectedPlanets(selectedPlanets ? [...selectedPlanets, planetName] : [planetName]); + } + } + + const handleInhabitantClick = (inhabitant: string) => { + if (selectedInhabitants?.includes(inhabitant)) { + setSelectedInhabitants(selectedInhabitants.filter(name => name !== inhabitant)); + } else { + setSelectedInhabitants(selectedInhabitants ? [...selectedInhabitants, inhabitant] : [inhabitant]); + } + } + + const handleObjectClick = (object: string) => { + if (selectedObjects?.includes(object)) { + setSelectedObjects(selectedObjects.filter(name => name !== object)); + } else { + setSelectedObjects(selectedObjects ? [...selectedObjects, object] : [object]); + } + } + + return ( +
+
+
+

Gallery Filters

+
+
+ +
+
+
+
+
+

Planet Type

+
+
+ {planetOptions.map(planet => ( +
+ {/*
*/} + handlePlanetClick(planet)} + checked={selectedPlanets?.includes(planet)} + /> + {/*
*/} +
+ {planet} +
+
+ ))} +
+
+ +
+
+

Inhabitants

+
+
+ {inhabitantOptions.map(inhabitant => ( +
+
+ handleInhabitantClick(inhabitant)} + checked={selectedInhabitants?.includes(inhabitant)} + /> +
+
+ {inhabitant} +
+
+ ))} +
+
+ +
+
+

Objects

+
+
+ {objectOptions.map(nftObject => ( +
+
+ handleObjectClick(nftObject)} + checked={selectedObjects?.includes(nftObject)} + /> +
+
+ {nftObject} +
+
+ ))} +
+
+
+
+ ); +}; diff --git a/frontend/src/components/filters/helpers.ts b/frontend/src/components/filters/helpers.ts new file mode 100644 index 0000000..3ec9f54 --- /dev/null +++ b/frontend/src/components/filters/helpers.ts @@ -0,0 +1,32 @@ +import { NFTType } from 'fakeData/mockNFTs'; +import { GalleryFiltersProps } from 'pages/nft/NFTs'; + +export const filterNFTs = ( + nfts: NFTType[], + filters: GalleryFiltersProps +) => { + return nfts.filter((nft) => { + let match = true; + + // Filter by planetNumber, if it's set + if (filters.planetNumber !== null) { + match = match && nft.id === filters.planetNumber; + } + + if (filters.planetNames.length !== 0) { + match = match && filters.planetNames.includes(nft.planet); + } + + // Filter by planetInhabitants, if it's set + if (filters.planetInhabitants.length !== 0) { + match = match && filters.planetInhabitants.includes(nft.character); + } + + // Filter by planetObjects, if it's set + if (filters.nftObjects.length !== 0) { + match = match && filters.nftObjects.includes(nft.object); + } + + return match; + }); +} \ No newline at end of file diff --git a/frontend/src/components/filters/options.ts b/frontend/src/components/filters/options.ts new file mode 100644 index 0000000..b82e3d7 --- /dev/null +++ b/frontend/src/components/filters/options.ts @@ -0,0 +1,88 @@ +export const planetOptions = [ + 'Cristall South', + 'Cristall North', + 'Crutha South', + 'Crutha North', + 'Gredica South', + 'Gredica North', + 'Kita South', + 'Kita North', + 'Lusa South', + 'Lusa North', + 'Minas South', + 'Minas North', + 'Ozara South', + 'Ozara North', + 'Pampas South', + 'Pampas North', + 'Sindari South', + 'Sindari North', + 'Zando South', + 'Zando North', +]; + +export const inhabitantOptions = [ + 'Cristallian F', + 'Cristallian M', + 'Cruthan F', + 'Cruthan M', + 'Gredican F', + 'Gredican M', + 'Kitan F', + 'Kitan M', + 'Lusan F', + 'Lusan M', + 'Minasan F', + 'Minasan M', + 'Ozaran F', + 'Ozaran M', + 'Pampan F', + 'Pampan M', + 'Sindarin F', + 'Sindarin M', + 'Zandoan F', + 'Zandoan M', +]; + +export const objectOptions = [ + 'Ancient Lusan Trident', + 'Battle Axe', + 'Battle Shovel', + 'Cristallian Bow', + 'Cristallian Ray Gun', + 'Cristallian Staff', + 'Cristallian Sword', + 'Cruthan Blaster', + 'Cruthan Death Mace', + 'Sindarin Flame Thrower', + 'Golden Hammer', + 'Gredican Power Staff', + 'Gredican Sword', + 'Ice Cleaver', + 'Kitan Ice Bow', + 'Kitan Ice Staff', + 'Kitan Ice Sword', + 'Lusan Water Saber', + 'Lusan Water Staff', + 'Lusan Xtreme Soaker', + 'Minasan Bow', + 'Minasan Ore Staff', + 'Minasan Ore Sword', + 'Ozaran Blaster', + 'Ozaran Bone Axe', + 'Ozaran Death Saber', + 'Ozaran Sand Staff', + 'Pampan Grass Staff', + 'Pampan Grass Sword', + 'Phoenix Rising', + 'Quartz Ray Gun', + 'Royal Ozaran Bow', + 'Sindarin Fire Bow', + 'Sindarin Fire Saber', + 'Sindarin Fire Staff', + 'SST Fishing Pole', + 'Sword of Zando', + 'The Eternal Torch', + 'Staff of Zando', + 'Zandoan Vine Bow', +]; \ No newline at end of file diff --git a/frontend/src/components/filters/search/SearchByID.module.scss b/frontend/src/components/filters/search/SearchByID.module.scss new file mode 100644 index 0000000..3f46aea --- /dev/null +++ b/frontend/src/components/filters/search/SearchByID.module.scss @@ -0,0 +1,48 @@ +.search__container { + display: flex; + align-items: center; + gap: 8px; + font-size: 14px; + font-weight: 500; + margin-bottom: 8px; + + .search__wrapper { + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; + padding: 10px 12px; + background: var(--token-light-500); + border: 1px solid var(--token-light-800); + border-radius: 8px; + width: 200px; + + &.focused { + background-color: var(--token-light-800); + border-color: var(--token-dark-700); + } + + input { + background: transparent; + border: none; + outline: none; + font-size: 14px; + font-weight: 500; + color: var(--token-dark-500); + width: 100%; + } + } + + + input[type="number"]::-webkit-inner-spin-button, + input[type="number"]::-webkit-outer-spin-button { + -webkit-appearance: none; + appearance: none; + margin: 0; + } + + /* Firefox */ + input[type="number"] { + -moz-appearance: textfield; + } +} \ No newline at end of file diff --git a/frontend/src/components/filters/search/SearchByID.tsx b/frontend/src/components/filters/search/SearchByID.tsx new file mode 100644 index 0000000..468dae2 --- /dev/null +++ b/frontend/src/components/filters/search/SearchByID.tsx @@ -0,0 +1,76 @@ +import React, { useState } from 'react'; +import classNames from 'classnames/bind'; +import { ReactComponent as SearchIcon } from 'assets/Search.svg' +import LoadingCircular from 'components/loading/circular'; +import styles from './SearchByID.module.scss'; + +const cx = classNames.bind(styles); + +export const SearchByID = ({ + setSearchValue, + searchValue, + isLoading, +}: { + setSearchValue: (value: string) => void; + searchValue: string; + isLoading: boolean; +}) => { + const [currentSearchValue, setCurrentSearchValue] = useState(searchValue); + const [focused, setFocused] = useState(false); + + const handleSearch = async (e: React.ChangeEvent) => { + const inputValue = e.target.value; + if (/^\d+$/.test(inputValue)) { + const intValue = parseInt(inputValue); + if (intValue >= 0 && intValue <= 10000) { + setCurrentSearchValue(inputValue); + } else { + setCurrentSearchValue(''); + } + } else if (inputValue === '') { + setCurrentSearchValue(inputValue); + } + }; + + const handleKeyPress = (e: React.KeyboardEvent) => { + if (e.key === 'Enter') { + e.preventDefault(); + setSearchValue(currentSearchValue); + } + }; + + const handleBlur = () => { + setFocused(false); + setSearchValue(currentSearchValue); + } + + return ( +
+
+ setFocused(true)} + onBlur={handleBlur} + min="1" + max="10000" + /> + +
+ {isLoading && ( +
+ +
+ )} +
+ ); +}; \ No newline at end of file diff --git a/frontend/src/components/loading/LoadingCircular.module.scss b/frontend/src/components/loading/LoadingCircular.module.scss new file mode 100644 index 0000000..403299e --- /dev/null +++ b/frontend/src/components/loading/LoadingCircular.module.scss @@ -0,0 +1,43 @@ +.circular__container { + width: 20px; + height: 20px; + display: flex; + display: inline-block; + color: var(--token-primary-500, #00C2FF); + animation: 1.4s linear 0s infinite normal none running rotate-animation; + + .circle { + stroke: currentcolor; + stroke-dasharray: 80px, 200px; + stroke-dashoffset: 0; + animation: 1.4s ease-in-out 0s infinite normal none running spin; + } +} + +@keyframes rotate-animation { + 0% { + transform: rotate(0deg); + } + + 100% { + transform: rotate(360deg); + } +} + + +@keyframes spin { + 0% { + stroke-dasharray: 1px, 200px; + stroke-dashoffset: 0; + } + 50% { + stroke-dasharray: 100px, 200px; + stroke-dashoffset: -15px; + } + 100% { + stroke-dasharray: 100px, 200px; + stroke-dashoffset: -125px; + } +} + + diff --git a/frontend/src/components/loading/circular.tsx b/frontend/src/components/loading/circular.tsx new file mode 100644 index 0000000..034d37a --- /dev/null +++ b/frontend/src/components/loading/circular.tsx @@ -0,0 +1,13 @@ +import styles from './LoadingCircular.module.scss'; + +const LoadingCircular = () => { + return ( +
+ + + +
+ ); +}; + +export default LoadingCircular; \ No newline at end of file diff --git a/frontend/src/components/navigations/desktop/DesktopNav.module.scss b/frontend/src/components/navigations/desktop/DesktopNav.module.scss index 6ce45bc..047370a 100644 --- a/frontend/src/components/navigations/desktop/DesktopNav.module.scss +++ b/frontend/src/components/navigations/desktop/DesktopNav.module.scss @@ -12,6 +12,16 @@ } } + .socials { + display: flex; + gap: 12px; + align-items: center; + a { + display: flex; + align-items: center; + } + } + .link__container { display: flex; align-items: center; @@ -23,10 +33,22 @@ color: var(--token-dark-500); font-size: 16px; font-weight: 600; + display: flex; + align-items: center; + gap: 4px; + + svg { + opacity: 0; + } &:hover { text-decoration: none; cursor: pointer; + + svg { + opacity: 1; + transition: all .2s ease; + } } } diff --git a/frontend/src/components/navigations/desktop/DesktopNav.tsx b/frontend/src/components/navigations/desktop/DesktopNav.tsx index c2fa7e2..78d286a 100644 --- a/frontend/src/components/navigations/desktop/DesktopNav.tsx +++ b/frontend/src/components/navigations/desktop/DesktopNav.tsx @@ -1,13 +1,18 @@ import classNames from 'classnames/bind'; import { Link, NavLink, useLocation } from 'react-router-dom'; import { ReactComponent as Logo } from 'assets/AllianceDAOLogo.svg'; +import { ReactComponent as ExternalLinkIcon } from 'assets/ExternalLink.svg'; import { ReactComponent as CheckIcon } from 'assets/check.svg'; +import { ReactComponent as TwitterIcon } from 'assets/socials/Twitter.svg'; +import { ReactComponent as MediumIcon } from 'assets/socials/Medium.svg'; +import { ReactComponent as TelegramIcon } from 'assets/socials/Telegram.svg'; import { useNav } from '../../../config/routes'; import styles from './DesktopNav.module.scss'; const cx = classNames.bind(styles); const DesktopNav = () => { + const socialSize = 16; const { pathname } = useLocation(); const { menu } = useNav(); @@ -21,7 +26,10 @@ const DesktopNav = () => { if (isExternal) { return (
  • - {name} + + {name} + +
  • ) } @@ -34,12 +42,25 @@ const DesktopNav = () => { ) })} - - - +
    + + + + +
    ); }; diff --git a/frontend/src/components/navigations/mobile/MobileNav.module.scss b/frontend/src/components/navigations/mobile/MobileNav.module.scss index 85eadbb..9c6c86a 100644 --- a/frontend/src/components/navigations/mobile/MobileNav.module.scss +++ b/frontend/src/components/navigations/mobile/MobileNav.module.scss @@ -99,6 +99,40 @@ color: white; font-size: 18px; font-weight: 600; + display: flex; + align-items: center; + gap: 4px; + + svg { + opacity: 0; + fill: white; + } + + &:hover { + text-decoration: none; + cursor: pointer; + + svg { + opacity: 1; + transition: all .2s ease; + } + } + } + } + } + + .bottom { + display: flex; + flex-direction: column; + gap: 32px; + + .socials { + display: flex; + gap: 24px; + align-items: center; + a { + display: flex; + align-items: center; } } } @@ -112,7 +146,7 @@ border-radius: 8px; background: var(--token-light-500); padding: 0px 24px; - margin-top: 48px; + // margin-top: 48px; color: var(--token-dark-500); font-size: 14px; diff --git a/frontend/src/components/navigations/mobile/MobileNav.tsx b/frontend/src/components/navigations/mobile/MobileNav.tsx index 9072a72..70f1a54 100644 --- a/frontend/src/components/navigations/mobile/MobileNav.tsx +++ b/frontend/src/components/navigations/mobile/MobileNav.tsx @@ -4,6 +4,10 @@ import { ReactComponent as Logo } from 'assets/AllianceDAOLogo.svg'; import { ReactComponent as HamburgerIcon } from 'assets/hamburger.svg'; import { ReactComponent as CloseIcon } from 'assets/close.svg'; import { ReactComponent as CheckIcon } from 'assets/check.svg'; +import { ReactComponent as ExternalLinkIcon } from 'assets/ExternalLink.svg'; +import { ReactComponent as TwitterIcon } from 'assets/socials/Twitter.svg'; +import { ReactComponent as MediumIcon } from 'assets/socials/Medium.svg'; +import { ReactComponent as TelegramIcon } from 'assets/socials/Telegram.svg'; import { useNav } from 'config/routes'; import styles from './MobileNav.module.scss'; @@ -16,6 +20,8 @@ const MobileNav = ({ isMobileNavOpen: boolean, setMobileNavOpen: (isMobileNavOpen: boolean) => void }) => { + const socialSize = 20; + const { pathname } = useLocation(); const { menu } = useNav(); @@ -44,7 +50,10 @@ const MobileNav = ({ if (isExternal) { return (
  • - {name} + + {name} + +
  • ) } @@ -57,12 +66,25 @@ const MobileNav = ({ ) })} - - - +
    + + + + +
    ); diff --git a/frontend/src/components/starmap/index.tsx b/frontend/src/components/starmap/index.tsx index f7f8da9..3fb7e85 100644 --- a/frontend/src/components/starmap/index.tsx +++ b/frontend/src/components/starmap/index.tsx @@ -2,7 +2,13 @@ import { useState } from 'react'; import { allPlanets, PlanetProps } from 'fakeData/planets'; import styles from './StarMap.module.scss'; -const StarMap = ({ planet, setPlanet }: { planet: string, setPlanet: (planet: PlanetProps) => void }) => { +const StarMap = ({ + planet, + setPlanet +}: { + planet: string, + setPlanet?: (planet: PlanetProps) => void +}) => { const [hoveredPlanet, setHoveredPlanet] = useState(null); const handlePlanetClick = (planetNumber: number) => { diff --git a/frontend/src/config/routes.tsx b/frontend/src/config/routes.tsx index 930fe5f..2d60d6b 100644 --- a/frontend/src/config/routes.tsx +++ b/frontend/src/config/routes.tsx @@ -33,7 +33,7 @@ export const useNav = () => { }, { path: "https://dao.enterprise.money/daos", - name: "Alliance DAO Staking", + name: "Staking", isExternal: true, }, ] diff --git a/frontend/src/content/TheStory.mdx b/frontend/src/content/TheStory.mdx index 8d97df3..6bb5262 100644 --- a/frontend/src/content/TheStory.mdx +++ b/frontend/src/content/TheStory.mdx @@ -5,9 +5,9 @@ import styles from './MDX.module.scss';
    Millions of years from now, there are ten planets hosting humanoid life. The planets and their inhabitants are wildly different, and none of them know the others exist, isolated by the vast emptiness of space. - + One day, something inexplicable happened. In a flash, hundreds of people on each planet suddenly vanished. They were transported to one of the other planets or to a different location on their home planet. Thousands were displaced and forced to make their way on strange new worlds. - + This occurrence is known simply as “The Warp.”
    @@ -36,11 +36,11 @@ import styles from './MDX.module.scss'; The Alliance devices themselves were also lost, and the thoughts of their use, the Last Journey, and the origins of humanity faded from memory. Some planets have ancient structures with inscriptions describing the Last Journey, but their true meaning has been lost as various cultures and religions rewrote their significance. - +
    ## Survival For the next seven million years, the inhabitants of the ten planets evolved and adapted to their surroundings, each becoming suited to the environment and climate of their homes. Their bodies changed, and so did the plants and animals around them. Ten planets created ten distinct cultures of human descendants, each with its own civilization and technology. - + The populations on each planet varied, though no planet ever became densely populated. Survival was difficult, even after millions of years. A few planets went through periods of near extinction, resetting civilization and beginning the cycle again. Some went through cycles of technological advancement followed by destruction and cultural decimation from wars and natural disasters. Several planets had achieved societal and technological advancements nearly on par with those of Earth. Despite their differences, the planets all had two things in common: none of them knew their origins, and most believed themselves to be the singular source of life across the cosmos.
    @@ -63,4 +63,3 @@ import styles from './MDX.module.scss'; - diff --git a/frontend/src/fakeData/mockNFTs.ts b/frontend/src/fakeData/mockNFTs.ts index 89abae7..6793ec8 100644 --- a/frontend/src/fakeData/mockNFTs.ts +++ b/frontend/src/fakeData/mockNFTs.ts @@ -1,87 +1,99 @@ +export interface NFTType { + id: number; + planet: string; + image: string; + biome: string; + character: string; + object: string; + rarityScore: number; + rewards: number; + claimed: string; +} + export const mockNFTs = [ { id: 0, - background_color: "", + planet: "Cristall South", image: "/src/assets/nfts/MOUNTAINS 2 1.png", biome: "water", - character: "fire", - object: "sword", + character: "Kitan F", + object: "Quartz Ray Gun", rarityScore: 0.4, rewards: 89.49, claimed: "24 December 2023" }, { id: 1, - background_color: "", + planet: "Crutha South", image: "/src/assets/nfts/MOUNTAINS 2 2.png", - biome: "water", - character: "fire", - object: "sword", + biome: "fire", + character: "Cristallian F", + object: "Sword of Zando", rarityScore: 0.4, rewards: 89.49, claimed: "24 December 2023" }, { id: 2, - background_color: "", + planet: "Kita South", image: "/src/assets/nfts/MOUNTAINS 2 3.png", - biome: "water", - character: "fire", - object: "sword", + biome: "ice", + character: "Kitan M", + object: "Staff of Zando", rarityScore: 0.4, rewards: 89.49, claimed: "24 December 2023" }, { id: 3, - background_color: "", + planet: "Kita North", image: "/src/assets/nfts/MOUNTAINS 2 4.png", - biome: "water", - character: "fire", - object: "sword", + biome: "jungle", + character: "Kitan F", + object: "Kitan Ice Staff", rarityScore: 0.4, rewards: 89.49, claimed: "24 December 2023" }, { id: 4, - background_color: "", + planet: "Ozara South", image: "/src/assets/nfts/MOUNTAINS 2 5.png", - biome: "water", - character: "fire", - object: "sword", + biome: "mountain", + character: "Pampan M", + object: "Cristallian Bow", rarityScore: 0.4, rewards: 89.49, claimed: "24 December 2023" }, { id: 5, - background_color: "", + planet: "Zando North", image: "/src/assets/nfts/MOUNTAINS 2 1second.png", - biome: "water", - character: "fire", - object: "sword", + biome: "meadows", + character: "Lusan M", + object: "Ice Cleaver", rarityScore: 0.4, rewards: 89.49, claimed: "24 December 2023" }, { id: 6, - background_color: "", + planet: "Sindari North", image: "/src/assets/nfts/MOUNTAINS 2 2second.png", - biome: "water", - character: "fire", - object: "sword", + biome: "asteroid", + character: "Sindarin M", + object: "Ice Cleaver", rarityScore: 0.4, rewards: 89.49, claimed: "24 December 2023" }, { id: 7, - background_color: "", + planet: "Sindari South", image: "/src/assets/nfts/MOUNTAINS 2 3second.png", - biome: "water", - character: "fire", + biome: "flowerbeds", + character: "Gredican M", object: "sword", rarityScore: 0.4, rewards: 89.49, @@ -89,82 +101,82 @@ export const mockNFTs = [ }, { id: 8, - background_color: "", + planet: "Minas South", image: "/src/assets/nfts/MOUNTAINS 2 4second.png", - biome: "water", - character: "fire", - object: "sword", + biome: "crystal", + character: "Kitan F", + object: "Pampan Grass Sword", rarityScore: 0.4, rewards: 89.49, claimed: "24 December 2023" }, { id: 9, - background_color: "", + planet: "Ozara South", image: "/src/assets/nfts/MOUNTAINS 2 5second.png", - biome: "water", - character: "fire", - object: "sword", + biome: "desert", + character: "Gredican F", + object: "Sindarin Fire Staff", rarityScore: 0.4, rewards: 89.49, claimed: "24 December 2023" }, { id: 10, - background_color: "", + planet: "Ozara North", image: "/src/assets/nfts/MOUNTAINS 2 1third.png", - biome: "water", - character: "fire", - object: "sword", + biome: "fire", + character: "Gredican F", + object: "Phoenix Rising", rarityScore: 0.4, rewards: 89.49, claimed: "24 December 2023" }, { id: 11, - background_color: "", + planet: "Gredica North", image: "/src/assets/nfts/MOUNTAINS 2 2third.png", biome: "water", - character: "fire", - object: "sword", + character: "Cruthan F", + object: "Lusan Xtreme Soaker", rarityScore: 0.4, rewards: 89.49, claimed: "24 December 2023" }, { id: 12, - background_color: "", + planet: "Gredica South", image: "/src/assets/nfts/MOUNTAINS 2 3third.png", biome: "water", character: "fire", - object: "sword", + object: "Golden Hammer", rarityScore: 0.4, rewards: 89.49, claimed: "24 December 2023" }, { id: 13, - background_color: "", + planet: "Crutha North", image: "/src/assets/nfts/MOUNTAINS 2 4third.png", biome: "water", - character: "fire", - object: "sword", + character: "Minas M", + object: "Sindarin Flame Thrower", rarityScore: 0.4, rewards: 89.49, claimed: "24 December 2023" }, { id: 14, - background_color: "", + planet: "Pampas South", image: "/src/assets/nfts/MOUNTAINS 2 5third.png", biome: "water", - character: "fire", - object: "sword", + character: "Minas M", + object: "Cruthan Death Mace", rarityScore: 0.4, rewards: 89.49, claimed: "24 December 2023" }, -]; +] as NFTType[]; export const signedInUserData = { id: 0, diff --git a/frontend/src/fakeData/planetsByName.tsx b/frontend/src/fakeData/planetsByName.tsx new file mode 100644 index 0000000..79a6987 --- /dev/null +++ b/frontend/src/fakeData/planetsByName.tsx @@ -0,0 +1,102 @@ +export const planetsByName = { + 'Cristall South': { + name: 'Cristall South', + terrain: 'crystal', + inhabitants: 'Cristall', + }, + 'Cristall North': { + name: 'Cristall North', + terrain: 'crystal', + inhabitants: 'Cristall', + }, + 'Crutha South': { + name: 'Crutha South', + terrain: 'mountain', + inhabitants: 'Crutha', + }, + 'Crutha North': { + name: 'Crutha North', + terrain: 'mountain', + inhabitants: 'Crutha', + }, + 'Gredica South': { + name: 'Gredica South', + terrain: 'flowerbeds', + inhabitants: 'Gredica', + }, + 'Gredica North': { + name: 'Gredica North', + terrain: 'flowerbeds', + inhabitants: 'Gredica', + }, + 'Kita South': { + name: 'Kita South', + terrain: 'ice', + inhabitants: 'Kita', + }, + 'Kita North': { + name: 'Kita North', + terrain: 'ice', + inhabitants: 'Kita', + }, + 'Lusa South': { + name: 'Lusa South', + terrain: 'water', + inhabitants: 'Lusa', + }, + 'Lusa North': { + name: 'Lusa North', + terrain: 'water', + inhabitants: 'Lusa', + }, + 'Minas South': { + name: 'Minas South', + terrain: 'asteroid', + inhabitants: 'Minas', + }, + 'Minas North': { + name: 'Minas North', + terrain: 'asteroid', + inhabitants: 'Minas', + }, + 'Ozara South': { + name: 'Ozara South', + terrain: 'desert', + inhabitants: 'Ozara', + }, + 'Ozara North': { + name: 'Ozara North', + terrain: 'desert', + inhabitants: 'Ozara', + }, + 'Pampas South': { + name: 'Pampas South', + terrain: 'meadows', + inhabitants: 'Pampas', + }, + 'Pampas North': { + name: 'Pampas North', + terrain: 'meadows', + inhabitants: 'Pampas', + }, + 'Sindari South': { + name: 'Sindari South', + terrain: 'fire', + inhabitants: 'Sindari', + }, + 'Sindari North': { + name: 'Sindari North', + terrain: 'fire', + inhabitants: 'Sindari', + }, + 'Zando South': { + name: 'Zando South', + terrain: 'jungle', + inhabitants: 'Zando', + }, + 'Zando North': { + name: 'Zando North', + terrain: 'jungle', + inhabitants: 'Zando', + }, +}; \ No newline at end of file diff --git a/frontend/src/pages/nft-view/NFTViewMobile.tsx b/frontend/src/pages/nft-view/NFTViewMobile.tsx index d4eac98..699d87d 100644 --- a/frontend/src/pages/nft-view/NFTViewMobile.tsx +++ b/frontend/src/pages/nft-view/NFTViewMobile.tsx @@ -4,7 +4,6 @@ import styles from './NFTViewMobile.module.scss'; interface NFTViewProps { nft: { id: number; - background_color: string; image: string; biome: string; character: string; @@ -61,7 +60,7 @@ export const NFTViewMobile = ({ nft }: NFTViewProps) => {
    - +
    diff --git a/frontend/src/pages/nft/NFTs.module.scss b/frontend/src/pages/nft/NFTs.module.scss index 2ab8b76..b710a41 100644 --- a/frontend/src/pages/nft/NFTs.module.scss +++ b/frontend/src/pages/nft/NFTs.module.scss @@ -1,54 +1,81 @@ .main { + display: flex; padding: 24px 80px; - .buttons { - display: flex; - align-items: center; - gap: 40px; - margin-bottom: 32px; - - .button { - color: #797979; - font-size: 18px; - font-weight: 600; - position: relative; - margin-bottom: 10px; - - .button__count { - display: flex; - align-items: center; - justify-content: center; - height: 20px; - width: 20px; - border-radius: 8px; - background: #797979; - color: var(--token-light-800, #F1EEE9); - text-align: center; - font-size: 14px; - font-weight: 700; - margin-left: 11px; - } + .facet__space { + flex: 0 0 0; + } + + &.facet__closed { + .facet__space { + flex: 0 0 0; + transition: flex-basis 0.5s cubic-bezier(0.22, 1, 0.36, 1); + } + } + + &.facet__open { + .facet__space { + flex: 0 0 20%; + transition: flex-basis 0.5s cubic-bezier(0.22, 1, 0.36, 1); + } + } + + .main__content { + flex: 1 1 0; - &::before { - position: absolute; - content: ""; - width: 0; - height: 2px; - background: var(--token-primary-500, #00C2FF); - left: 50%; - bottom: -10px; - transform: translateX(-50%); - border-radius: 10px; - transition: all .2s ease; - opacity: 0; + .buttons { + display: flex; + align-items: center; + gap: 40px; + margin-bottom: 32px; + + svg { + margin-bottom: 8px; } - &.button__selected { - color: var(--token-dark-500, #2F2E2D); + .button { + color: #797979; + font-size: 18px; + font-weight: 600; + position: relative; + margin-bottom: 10px; + + .button__count { + display: flex; + align-items: center; + justify-content: center; + height: 20px; + width: 20px; + border-radius: 8px; + background: #797979; + color: var(--token-light-800, #F1EEE9); + text-align: center; + font-size: 14px; + font-weight: 700; + margin-left: 11px; + } &::before { - width: 100%; - opacity: 1; + position: absolute; + content: ""; + width: 0; + height: 2px; + background: var(--token-primary-500, #00C2FF); + left: 50%; + bottom: -10px; + transform: translateX(-50%); + border-radius: 10px; + transition: all .2s ease; + opacity: 0; + } + + &.button__selected { + color: var(--token-dark-500, #2F2E2D); + + &::before { + width: 100%; + opacity: 1; + } } } } diff --git a/frontend/src/pages/nft/NFTs.tsx b/frontend/src/pages/nft/NFTs.tsx index 256cab2..be170f9 100644 --- a/frontend/src/pages/nft/NFTs.tsx +++ b/frontend/src/pages/nft/NFTs.tsx @@ -1,49 +1,140 @@ -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import classNames from 'classnames/bind'; import NFTItem from 'components/nft/NFTItem'; -import styles from './NFTs.module.scss'; +import { filterNFTs } from 'components/filters/helpers'; +import { FilterDropdowns } from 'components/filters/dropdowns'; +import { ReactComponent as FilterIcon } from 'assets/Filter.svg'; import { mockNFTs, signedInUserData } from 'fakeData/mockNFTs'; +import styles from './NFTs.module.scss'; +import { SearchByID } from 'components/filters/search/SearchByID'; const cx = classNames.bind(styles); +export interface GalleryFiltersProps { + planetNumber: number | null + planetNames: string[] + planetInhabitants: string[] + nftObjects: string[] +} + export const NFTsPage = () => { - const [isMyNFTSelected, setIsMyNFTSelected] = useState(false); + const [showFilterRow, setShowFilterRow] = useState(false); + const [searchValue, setSearchValue] = useState(''); + const [searchLoading, setSearchLoading] = useState(false); + + const [activeTab, setActiveTab] = useState('all'); + const [galleryFilters, setGalleryFilters] = useState({ + planetNumber: null, + planetNames: [], + planetInhabitants: [], + nftObjects: [], + }); + + const [displayedNFTs, setDisplayedNFTs] = useState(mockNFTs); + + useEffect(() => { + if (!galleryFilters.planetNames.length && !galleryFilters.planetInhabitants.length && !galleryFilters.nftObjects.length) { + setDisplayedNFTs(mockNFTs); + return; + } + + const filtered = filterNFTs(mockNFTs, galleryFilters); + setDisplayedNFTs(filtered); + }, [galleryFilters]); + + useEffect(() => { + if (!searchValue) { + setSearchLoading(false); + setDisplayedNFTs(mockNFTs); + return; + } + + setSearchLoading(true); + setTimeout(() => { + const filtered = mockNFTs.filter(nft => nft.id.toString() === searchValue); + setDisplayedNFTs(filtered); + setSearchLoading(false); + }, 1000) + + }, [searchValue]); + + const handleSwitch = (tab: string) => { + if (tab === 'all' && activeTab === 'all') { + return; + } else if (tab === 'my' && activeTab === 'my') { + return; + } + + if (tab === 'all') { + setActiveTab('all'); + setShowFilterRow(false); + } else { + setActiveTab('my'); + setShowFilterRow(false); + } + } return (
    -
    - - -
    - {!isMyNFTSelected ? ( -
    - {mockNFTs.map(nft => ( - - ))} +
    +
    + {activeTab === 'all' && ( + + )} + +
    - ) : ( -
    - {signedInUserData.nftIDs.map(nft => ( - + - ))} -
    - )} + + + )} + + {activeTab === 'all' ? ( +
    + {displayedNFTs.map(nft => ( + + ))} +
    + ) : ( +
    + {signedInUserData.nftIDs.map(nft => ( + + ))} +
    + )} +
    ); }; diff --git a/frontend/src/styles/_reboot.scss b/frontend/src/styles/_reboot.scss index 9116c74..ff88a86 100644 --- a/frontend/src/styles/_reboot.scss +++ b/frontend/src/styles/_reboot.scss @@ -137,7 +137,7 @@ select { input, textarea { - width: 100%; + // width: 100%; } textarea {