diff --git a/packages/examples/sdk-frontend-react/src/app/app.tsx b/packages/examples/sdk-frontend-react/src/app/app.tsx index 98a5bd2c7..e4e7d2303 100644 --- a/packages/examples/sdk-frontend-react/src/app/app.tsx +++ b/packages/examples/sdk-frontend-react/src/app/app.tsx @@ -313,7 +313,7 @@ export function App() { - + { const [selectedTypeValue, setSelectedTypeValue] = useState(0); + const [validationErrors, setValidationErrors] = + useState({}); const [selectedCategoryValue, setSelectedCategoryValue] = useState(0); const [selectedSubCategoryValue, setSelectedSubCategoryValue] = useState(0); - const [guildComparison, setGuildComparison] = useState('') + const [validationLoading, setValidationLoading] = useState(false); + const [guildComparison, setGuildComparison] = useState(''); const [selectedChainValue, setSelectedChainValue] = useState(0); const [contract, setContract] = useState(''); const [inviteCheckboxes, setInviteCheckboxes] = useState<{ @@ -56,6 +70,8 @@ const AddCriteria = ({ const [url, setUrl] = useState(''); const [guildId, setGuildId] = useState(''); const [specificRoleId, setSpecificRoleId] = useState(''); + const [unit, setUnit] = useState('TOKEN'); + const [decimals, setDecimals] = useState(18); const [quantity, setQuantity] = useState<{ value: number; range: number }>({ value: 0, @@ -63,6 +79,7 @@ const AddCriteria = ({ }); const { env } = useChatData(); const theme = useContext(ThemeContext); + const groupInfoToast = useToast(); const isMobile = useMediaQuery(device.mobileL); @@ -151,23 +168,15 @@ const AddCriteria = ({ }, }; - const tokenCategoryValues = [ - { - id: 0, + const dropdownSubCategoryValues: DropdownSubCategoryValuesType = { + ERC20: { value: SUBCATEGORY.HOLDER, title: 'Holder', - function: () => setSelectedSubCategoryValue(0), }, - { - id: 1, + ERC721:{ value: SUBCATEGORY.OWENER, title: 'Owner', - function: () => setSelectedSubCategoryValue(1), }, - ]; - const dropdownSubCategoryValues: DropdownSubCategoryValuesType = { - ERC20: tokenCategoryValues, - ERC721: tokenCategoryValues, INVITE: { value: SUBCATEGORY.DEFAULT, title: 'Default', @@ -284,133 +293,207 @@ const AddCriteria = ({ setQuantity({ ...quantity, value: e.target.value }); }; - const verifyAndDoNext = ()=>{ - const _type = dropdownTypeValues[selectedTypeValue].value as 'PUSH' | 'GUILD' - const category:string = _type === "PUSH" ? (dropdownCategoryValues[_type] as DropdownValueType[])[ - selectedCategoryValue - ].value || CATEGORY.ERC20 : "ROLES" - - let subCategory = "DEFAULT" - if(_type === "PUSH"){ - if(category === CATEGORY.ERC20 || category === CATEGORY.ERC721){ - subCategory = tokenCategoryValues[selectedSubCategoryValue].value - }else if(category === CATEGORY.CustomEndpoint){ - subCategory = "GET" - } + const verifyAndDoNext = async () => { + setValidationLoading(true); + const _type = dropdownTypeValues[selectedTypeValue].value as + | 'PUSH' + | 'GUILD'; + const category: string = + _type === 'PUSH' + ? (dropdownCategoryValues[_type] as DropdownValueType[])[ + selectedCategoryValue + ].value || CATEGORY.ERC20 + : 'ROLES'; + + let subCategory = 'DEFAULT'; + if (_type === 'PUSH') { + if (category === CATEGORY.ERC20 || category === CATEGORY.ERC721) { + subCategory = category === CATEGORY.ERC20 ? SUBCATEGORY.HOLDER : SUBCATEGORY.OWENER + } else if (category === CATEGORY.CustomEndpoint) { + subCategory = 'GET'; + } } - - const getData = (type:string, category:string):Data=>{ - if(type === "PUSH"){ - if(category === CATEGORY.ERC20 || category === CATEGORY.ERC721){ - const selectedChain = dropdownChainsValues[selectedChainValue].value || 'eip155:1'; + + const getData = (type: string, category: string): Data => { + if (type === 'PUSH') { + if (category === CATEGORY.ERC20 || category === CATEGORY.ERC721) { + const selectedChain = + dropdownChainsValues[selectedChainValue].value || 'eip155:1'; return { contract: `${selectedChain}:${contract}`, amount: quantity.value, - comparison:dropdownQuantityRangeValues[quantity.range].value, - decimals: 18, - } - }else if(category === CATEGORY.INVITE){ - const _inviteRoles = [] - if(inviteCheckboxes.admin){ - _inviteRoles.push("ADMIN") + comparison: dropdownQuantityRangeValues[quantity.range].value, + decimals: category === CATEGORY.ERC20 ? decimals : undefined, + token: unit, + }; + } else if (category === CATEGORY.INVITE) { + const _inviteRoles = []; + if (inviteCheckboxes.admin) { + _inviteRoles.push('ADMIN'); } - if(inviteCheckboxes.owner){ - _inviteRoles.push("OWNER") + if (inviteCheckboxes.owner) { + _inviteRoles.push('OWNER'); } - return{ - inviterRoles: _inviteRoles as ['OWNER' | 'ADMIN'] - } - }else{ - // CATEGORY.CustomEndpoint - // TODO: validate url - return{ - url:url - } + return { + inviterRoles: _inviteRoles as ['OWNER' | 'ADMIN'], + }; + } else { + return { + url: url, + }; } - }else{ - // GUILD type + } else { return { - id:guildId, - comparison:guildComparison, - role:guildComparison === 'specific' ? specificRoleId : "*", - } + id: guildId, + comparison: guildComparison === 'specific' ? '' : guildComparison, + role: guildComparison === 'specific' ? specificRoleId : '*', + }; } - } + }; - const rule:Rule = { + const rule: Rule = { type: _type, category: category, subcategory: subCategory, data: getData(_type, category), + }; + + //guild validation added + const errors = await validationCriteria(rule); + setValidationLoading(false); + if (Object.keys(errors).length) { + setValidationErrors(errors); + } else { + const isSuccess = criteriaState.addNewRule(rule); + if (!isSuccess) { + showError('Selected Criteria was already added'); + return; + } + if (handlePrevious) { + handlePrevious(); + } } + }; - criteriaState.addNewRule(rule) + const criteriaState = criteriaStateManager.getSelectedCriteria(); - if(handlePrevious){ - handlePrevious() - } + // Autofill the form for the update + useEffect(() => { + if (criteriaState.isUpdateCriteriaEnabled()) { + //Load the states + const oldValue = + criteriaState.selectedRules[criteriaState.updateCriteriaIdx]; + + if (oldValue.type === 'PUSH') { + // category + setSelectedCategoryValue( + (dropdownCategoryValues.PUSH as DropdownValueType[]).findIndex( + (obj) => obj.value === oldValue.category + ) + ); - } + const pushData = oldValue.data as PushData; - const criteriaState = criteriaStateManager.getSelectedCriteria() + // sub category + if ( + oldValue.category === CATEGORY.ERC20 || + oldValue.category === CATEGORY.ERC721 + ) { + + if(pushData.token){ + setUnit(pushData.token) + } + if (pushData.decimals) { + setDecimals(decimals); + } - // Autofill the form for the update - useEffect(()=>{ - if(criteriaState.isUpdateCriteriaEnabled()){ - //Load the states - const oldValue = criteriaState.selectedRules[criteriaState.updateCriteriaIdx] - - if(oldValue.type === 'PUSH'){ - - // category - setSelectedCategoryValue( - (dropdownCategoryValues.PUSH as DropdownValueType[]).findIndex(obj => obj.value === oldValue.category) - ) - - const pushData = oldValue.data as PushData - - // sub category - if(oldValue.category === CATEGORY.ERC20 || oldValue.category === CATEGORY.ERC721){ - setSelectedSubCategoryValue( - tokenCategoryValues.findIndex(obj => obj.value === oldValue.subcategory) - ) - - const contractAndChain:string[] = (pushData.contract || "eip155:1:0x").split(':') - setSelectedChainValue( - dropdownChainsValues.findIndex( - obj => obj.value === contractAndChain[0]+":"+contractAndChain[1] - ) - ) - setContract(contractAndChain.length === 3 ? contractAndChain[2]: "") - setQuantity({ - value:pushData.amount || 0, - range:dropdownQuantityRangeValues.findIndex( - obj => obj.value === pushData.comparison - ) - }) - }else if(oldValue.category === CATEGORY.INVITE){ - setInviteCheckboxes({ - admin:true, - owner:true, - }) - }else{ - // invite - setUrl(pushData.url || "") + // TODO: make helper function for this + const contractAndChain: string[] = ( + pushData.contract || 'eip155:1:0x' + ).split(':'); + setSelectedChainValue( + dropdownChainsValues.findIndex( + (obj) => + obj.value === contractAndChain[0] + ':' + contractAndChain[1] + ) + ); + setContract(contractAndChain.length === 3 ? contractAndChain[2] : ''); + setQuantity({ + value: pushData.amount || 0, + range: dropdownQuantityRangeValues.findIndex( + (obj) => obj.value === pushData.comparison + ), + }); + } else if (oldValue.category === CATEGORY.INVITE) { + setInviteCheckboxes({ + admin: true, + owner: true, + }); + } else { + // invite + setUrl(pushData.url || ''); + } + } else { + // guild condition + setGuildId((oldValue.data as GuildData).id); + setSpecificRoleId((oldValue.data as GuildData).role); + setGuildComparison(((oldValue.data as GuildData).comparison) || GUILD_COMPARISON_OPTIONS[2].value); } - }else{ - // guild condition - setGuildId((oldValue.data as GuildData).id) - setSpecificRoleId((oldValue.data as GuildData).role) - setGuildComparison((oldValue.data as GuildData).comparison) + + setSelectedTypeValue( + dropdownTypeValues.findIndex((obj) => obj.value === oldValue.type) + ); } - - setSelectedTypeValue( - dropdownTypeValues.findIndex(obj => obj.value === oldValue.type) - ) - } - },[]) + }, []); + + const getSeletedType = () => { + return dropdownTypeValues[selectedTypeValue].value || 'PUSH'; + }; + + const getSelectedCategory = () => { + const category: string = + (dropdownCategoryValues['PUSH'] as DropdownValueType[])[ + selectedCategoryValue + ].value || CATEGORY.ERC20; + + return category; + }; + + const getSelectedChain = () => { + return dropdownChainsValues[selectedChainValue].value || 'eip155:1'; + }; + + // Fetch the contract info + useEffect(() => { + // TODO: optimize to reduce this call call when user is typing + (async()=>{ + setValidationErrors(prev => ({...prev, tokenError:undefined})) + + const _type = getSeletedType(); + const _category: string = getSelectedCategory(); + const _chainInfo = getSelectedChain(); + + await tokenFetchHandler( + contract, + _type, + _category, + _chainInfo, + setUnit, + setDecimals + ); + })(); + }, [contract, selectedCategoryValue, selectedChainValue]); + + const showError = (errorMessage: string) => { + groupInfoToast.showMessageToast({ + toastTitle: 'Error', + toastMessage: errorMessage, + toastType: 'ERROR', + getToastIcon: (size) => , + }); + }; return (
- setContract(e.target.value)} - placeholder="e.g. 0x123..." - /> +
+ setContract(e.target.value)} + placeholder="e.g. 0x123..." + error={!!validationErrors?.tokenError} + /> + {!!validationErrors?.tokenError && ( + {validationErrors?.tokenError} + )} +
+
+ {!!validationErrors?.tokenAmount && ( + {validationErrors?.tokenAmount} + )} +
)} {checkIfCustomEndpoint() && ( +
setUrl(e.target.value)} placeholder="e.g. abc.com" + error={!!validationErrors?.url} /> + {!!validationErrors?.url && ( + {validationErrors?.url} + )} +
)} {checkIfPushInvite() && (
@@ -505,8 +612,8 @@ const AddCriteria = ({ } onToggle={() => setInviteCheckboxes({ - admin:true, - owner:true + admin: true, + owner: true, }) } checked={ @@ -519,34 +626,52 @@ const AddCriteria = ({ {checkIfGuild() && ( <> - setGuildId(e.target.value)} - placeholder="e.g. 4687" - /> - { - setGuildComparison(newEl)}} - /> - - {guildComparison === "specific" && +
setSpecificRoleId(e.target.value)} + labelName="ID" + inputValue={guildId} + onInputChange={(e: any) => setGuildId(e.target.value)} placeholder="e.g. 4687" + error={!!validationErrors?.guildId} /> - } - - + {!!validationErrors?.guildId && ( + {validationErrors?.guildId} + )} +
+
+ { + setGuildComparison(newEl); + }} + /> + {!!validationErrors?.guildComparison && ( + {validationErrors?.guildComparison} + )} +
+ {guildComparison === 'specific' && ( +
+ setSpecificRoleId(e.target.value)} + placeholder="e.g. 4687" + error={!!validationErrors?.guildRole} + /> + {!!validationErrors?.guildRole && ( + {validationErrors?.guildRole} + )} +
+ )} )}
@@ -555,3 +680,8 @@ const AddCriteria = ({ export default AddCriteria; +const ErrorSpan = styled(Span)` + font-size: 12px; + font-weight: 500; + color: #ed5858; +`; diff --git a/packages/uiweb/src/lib/components/chat/CreateGroup/ConditionsComponent.tsx b/packages/uiweb/src/lib/components/chat/CreateGroup/ConditionsComponent.tsx index 4efb0d594..db704210a 100644 --- a/packages/uiweb/src/lib/components/chat/CreateGroup/ConditionsComponent.tsx +++ b/packages/uiweb/src/lib/components/chat/CreateGroup/ConditionsComponent.tsx @@ -8,7 +8,7 @@ import { ThemeContext } from '../theme/ThemeProvider'; import Dropdown, { DropdownValueType } from '../reusables/DropDown'; import { ConditionArray, ConditionData, IChatTheme } from '../exportedTypes'; import { useClickAway } from '../../../hooks'; -import { CATEGORY, CRITERIA_TYPE, CriteriaType, TOKEN_NFT_COMPARISION, TokenNftComparision } from '../types'; +import { CATEGORY, CRITERIA_TYPE, CriteriaType, PushData, TOKEN_NFT_COMPARISION, TokenNftComparision } from '../types'; import EditSvg from '../../../icons/EditSvg.svg'; import RemoveSvg from '../../../icons/RemoveSvg.svg'; @@ -86,14 +86,31 @@ const CriteriaSection = ({ criteria }: { criteria: ConditionData }) => { }; const getGuildRole = () =>{ + console.log(criteria?.data?.['comparison']) + if(!criteria?.data?.['comparison']) + { + return 'SPECIFIC'; + } return (GUILD_COMPARISON_OPTIONS.find(option => option.value === criteria?.data?.['comparison']))?.heading; } + + const getTokenSymbol = (conditionData:ConditionData)=>{ + if(conditionData.data){ + const data:PushData = conditionData.data; + if(data.token){ + return shortenText(data.token, 15) + } + } + + return conditionData.category + } + return (
{ {getTokenNftComparisionLabel()}{' '} {/* need to fetch token symbol */} - {criteria?.data?.['amount']} {criteria.category} + {criteria?.data?.['amount']} {getTokenSymbol(criteria)} )} {criteria.category === CATEGORY.INVITE && ( diff --git a/packages/uiweb/src/lib/components/chat/CreateGroup/CreateGroupModal.tsx b/packages/uiweb/src/lib/components/chat/CreateGroup/CreateGroupModal.tsx index 9b2ad7045..49add35d8 100644 --- a/packages/uiweb/src/lib/components/chat/CreateGroup/CreateGroupModal.tsx +++ b/packages/uiweb/src/lib/components/chat/CreateGroup/CreateGroupModal.tsx @@ -26,6 +26,7 @@ import { import { Image } from '../../../config/styles'; import { ProfilePicture, device } from '../../../config'; +import { CriteriaValidationErrorType } from '../types'; export const CREATE_GROUP_STEP_KEYS = { INPUT_DETAILS: 1, @@ -69,12 +70,12 @@ export const CreateGroupModal: React.FC = ({ } }, [activeComponent]); - const [groupInputDetails, setGroupInputDetails] = - useState({ - groupName: '', - groupDescription: '', - groupImage: '', - }); + const useDummyGroupInfo = false; + const [groupInputDetails, setGroupInputDetails] = useState({ + groupName: useDummyGroupInfo ? 'This is duumy group name' : '', + groupDescription: useDummyGroupInfo ? 'This is dummy group description for testing' : '', + groupImage: useDummyGroupInfo ? ProfilePicture : '' + }) const renderComponent = () => { switch (activeComponent) { @@ -162,7 +163,8 @@ const CreateGroupDetail = ({ const groupInfoToast = useToast(); const { groupName, groupDescription, groupImage } = groupInputDetails; const theme = useContext(ThemeContext); - + const [validationErrors, setValidationErrors] = + useState({}); const fileUploadInputRef = useRef(null); const isMobile = useMediaQuery(device.mobileL); @@ -205,21 +207,20 @@ const CreateGroupDetail = ({ if (!skipVerify) { // verify name if (groupName.trim().length === 0) { - showError('Group Name is empty'); + setValidationErrors({ + groupName: 'Group name cannot be empty', + }); return; } // verify description if (groupDescription.trim().length === 0) { - showError('Group Description is empty'); + setValidationErrors({ + groupDescription: 'Group Description is empty', + }); return; } - // verify description - // if (!groupImage) { - // showError("Group image can't be empty"); - // return; - // } } if (handleNext) { @@ -268,31 +269,42 @@ const CreateGroupDetail = ({ onChange={(e) => handleChange(e as unknown as Event)} /> - - setGroupInputDetails({ - groupDescription, - groupName: e.target.value, - groupImage, - }) - } - /> - -