-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #70 from DevNode-Dev/feat/community-tags
community tags
- Loading branch information
Showing
18 changed files
with
507 additions
and
86 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
94 changes: 94 additions & 0 deletions
94
apps/web/src/components/MultiSelectDropdown/MultiSelectDropdown.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
import {Fragment, useState} from 'react' | ||
import {Combobox, Transition} from '@headlessui/react' | ||
import {CheckIcon} from '@heroicons/react/20/solid' | ||
import {multiSelectProps} from "./types"; | ||
import {toast} from "react-toastify"; | ||
|
||
|
||
const MultiSelectDropdown = (props: multiSelectProps) => { | ||
const {dataArray, selectedData, setData, NoDataComponent, maxLimit, attribute } = props; | ||
const [query, setQuery] = useState('') | ||
|
||
const filteredPeople = | ||
query === '' | ||
? null | ||
: dataArray.filter((dataValue) => | ||
dataValue[attribute] | ||
.toLowerCase() | ||
.replace(/\s+/g, '') | ||
.includes(query.toLowerCase().replace(/\s+/g, '')) | ||
) | ||
const handleChange = (value) =>{ | ||
if(selectedData.length === maxLimit){ | ||
toast.error(`maximum of ${maxLimit} tags can be used`); | ||
return; | ||
} | ||
setData(value); | ||
} | ||
return ( | ||
<div > | ||
<Combobox value={selectedData} onChange={handleChange as any} multiple> | ||
<div className="relative mt-1"> | ||
<div | ||
className="relative w-full cursor-default overflow-hidden rounded-lg bg-white text-left focus:outline-none focus-visible:ring-2 focus-visible:ring-white focus-visible:ring-opacity-75 focus-visible:ring-offset-2 focus-visible:ring-offset-teal-300 sm:text-sm"> | ||
<Combobox.Input | ||
className="w-full py-2 pl-3 pr-10 text-sm leading-5 text-gray-900 rounded-md border-2 border-solid border-gray-200 text-sm leading-5 text-gray-900 focus:ring-0" | ||
displayValue={() => ""} | ||
onChange={(event) => setQuery(event.target.value)} | ||
placeholder="Search Tag" | ||
/> | ||
</div> | ||
{(filteredPeople !== null) && (<Transition | ||
as={Fragment} | ||
leave="transition ease-in duration-100" | ||
leaveFrom="opacity-100" | ||
leaveTo="opacity-0" | ||
afterLeave={() => setQuery('')} | ||
> | ||
<Combobox.Options | ||
as={"div"} | ||
className="absolute mt-1 max-h-[100px] w-full overflow-auto rounded-md border-2 border-solid border-gray-200 bg-white shadow-lg text-base ring-1 ring-black ring-opacity-5 focus:outline-none sm:text-sm scrollbar-hide"> | ||
{filteredPeople.length === 0 && query !== '' ? ( | ||
<div className="relative cursor-default select-none py-2 px-4 text-gray-700"> | ||
{NoDataComponent(query)} | ||
</div> | ||
) : ( | ||
filteredPeople.map((dataValue, index) => ( | ||
<Combobox.Option | ||
key={index} | ||
className={({active}) => | ||
`relative cursor-default select-none py-2 pl-10 pr-4 border-b hover:bg-slate-200 text-gray-900` | ||
} | ||
as={"div"} | ||
value={dataValue} | ||
> | ||
{({selected, active}) => ( | ||
<> | ||
<span | ||
className={`block truncate ${ | ||
selected ? 'font-medium' : 'font-normal' | ||
}`} | ||
> | ||
|
||
{dataValue[attribute]} | ||
</span> | ||
{selected ? ( | ||
<span | ||
className={`absolute inset-y-0 left-0 flex items-center pl-3 text-gray-900 `} | ||
> | ||
<CheckIcon className="h-5 w-5" aria-hidden="true"/> | ||
</span> | ||
) : null} | ||
</> | ||
)} | ||
</Combobox.Option> | ||
)) | ||
)} | ||
</Combobox.Options> | ||
</Transition>)} | ||
</div> | ||
</Combobox> | ||
</div> | ||
) | ||
} | ||
export default MultiSelectDropdown; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export {default as MultiSelectDropdown} from "./MultiSelectDropdown" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
export type multiSelectProps = { | ||
dataArray:object[], | ||
selectedData: object[], | ||
setData : React.Dispatch<React.SetStateAction<{id: string, tag: string}[]>> | ||
NoDataComponent? : (query:string) => JSX.Element; | ||
maxLimit: number; | ||
attribute: string; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import {CreateTagProps} from "./types"; | ||
import {trpc} from "../../utils/trpc"; | ||
import {useAppSelector} from "../../store"; | ||
import {isNil} from "lodash"; | ||
import {useState} from "react"; | ||
import {toast} from "react-toastify"; | ||
import {isRight} from "../../utils/fp"; | ||
import {PrimaryButton} from "../Button"; | ||
|
||
const CreateTag = (props: CreateTagProps) => { | ||
const [loading, setLoading] = useState<boolean>(false); | ||
const {title, tag, refetch} = props; | ||
const user = useAppSelector(state => state.user); | ||
const createTag = trpc.tag.createTag.useMutation(); | ||
|
||
const handleCreateTag = async () => { | ||
if (isNil(user.id) || isNil(user.didSession)) { | ||
toast.error("Please re-connect with your wallet") | ||
} | ||
setLoading(true); | ||
const response = await createTag.mutateAsync({ | ||
session:user.didSession, | ||
tag:tag | ||
}); | ||
if(isRight(response)){ | ||
toast.success("tag created"); | ||
refetch(); | ||
} | ||
else { | ||
toast.error("Failed to create tag. Try again in a while!"); | ||
} | ||
setLoading(false); | ||
} | ||
|
||
return ( | ||
<div> | ||
<PrimaryButton | ||
title={loading ? "creating.." : title} | ||
onClick={handleCreateTag} | ||
loading={loading} | ||
/> | ||
</div> | ||
) | ||
} | ||
export default CreateTag; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,68 @@ | ||
import {useState} from "react"; | ||
import {has, isEmpty} from "lodash"; | ||
import {MultiSelectDropdown} from "../MultiSelectDropdown"; | ||
import {trpc} from "../../utils/trpc"; | ||
import {tagSelectProp} from "./types"; | ||
import {Chip} from "../Chip"; | ||
import {CreateTag} from "./index"; | ||
|
||
const TagMultiSelect = (props: tagSelectProp) => { | ||
|
||
const {selectedData, setData, placeholder} = props; | ||
const tagData = trpc.tag.getAllTags.useQuery(); | ||
const [open, setOpen] = useState<boolean>(false); | ||
|
||
const tagFilteredData = has(tagData, "data.value") ? tagData.data?.value.map((tag) => tag.node) : []; | ||
|
||
const handleRefetch = async () => { | ||
await tagData.refetch() | ||
} | ||
const handleOpen = () => { | ||
setOpen((prevstate: boolean) => !prevstate) | ||
} | ||
const handleCloseTag = (removedTag) => { | ||
setData((prevState) => prevState.filter((tag) => tag !== removedTag)); | ||
} | ||
return ( | ||
<div className={"relative w-full h-full"}> | ||
<div className={"flex row w-full h-full items-center "}> | ||
<div className={"flex row gap-[10px] w-full p-3 flex-wrap"}> | ||
{isEmpty(selectedData) && <div className={"text-sm text-[#A2A8B4]"}> {placeholder} </div>} | ||
{selectedData && selectedData.length > 0 ? (selectedData.map((tag, index) => { | ||
return (<Chip key={index} text={tag.tag} onClose={() => { | ||
handleCloseTag(tag) | ||
}}/>) | ||
})) : null} | ||
</div> | ||
<div className={"w-[30px] h-[30px] text-center"} onClick={handleOpen}> | ||
<svg className="w-full h-full cursor-pointer" viewBox="0 0 20 20" fill="currentColor"> | ||
<path fillRule="evenodd" d="M10 14l-5-5h10l-5 5z" clipRule="evenodd"/> | ||
</svg> | ||
</div> | ||
</div> | ||
{open && !isEmpty(tagFilteredData) && ( | ||
<div className={"absolute bottom-[-60px] w-full h-12"}> | ||
<MultiSelectDropdown | ||
dataArray={tagFilteredData} | ||
selectedData={selectedData} | ||
setData={setData} | ||
maxLimit={5} | ||
attribute={"tag"} | ||
NoDataComponent={(query: string) => ( | ||
<> | ||
<div className={"w-full h-full flex row items-center"}> | ||
<div className={"grow inline-block w-[30%]"}> | ||
{"No tag found"} | ||
</div> | ||
<div className={"w-[70%"}> | ||
<CreateTag tag={query} title={"create tag"} refetch={handleRefetch}/> | ||
</div> | ||
</div> | ||
</> | ||
)} | ||
/> | ||
</div>)} | ||
</div> | ||
) | ||
} | ||
export default TagMultiSelect; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export {default as TagMultiSelect} from "./TagMultiSelect"; | ||
export {default as CreateTag} from "./CreateTag"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
|
||
export type tagSelectProp = { | ||
selectedData: {id: string, tag: string}[], | ||
setData : React.Dispatch<React.SetStateAction<{id: string, tag: string}[]>> | ||
placeholder: string | ||
} | ||
|
||
export interface CreateTagProps { | ||
title: string; | ||
classes?: string; | ||
disabled?: boolean; | ||
loading?: boolean; | ||
tag: string; | ||
onClick?: () => void; | ||
refetch: () => void; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.