diff --git a/src/components/Input.tsx b/src/components/Input.tsx index 9f2d22f4..ca85b4aa 100644 --- a/src/components/Input.tsx +++ b/src/components/Input.tsx @@ -1,83 +1,94 @@ import { InputAdornment, OutlinedInput, Typography } from '@mui/material' import React, { useMemo } from 'react' import { actionTypes, usePartnerConfigurationContext } from '../helpers/partnerConfigurationContext' +import useWalletBalance from '../helpers/useWalletBalance' const Input = ({ ...rest }) => { const { state, dispatch } = usePartnerConfigurationContext() - const error = useMemo(() => { - if (!state.balance) return true - let balance = parseFloat(state.balance) - return balance < 100 - }, [state]) - + const { balance: maxBalance } = useWalletBalance() const handleChange = e => { const newAmount = e.target.value if (newAmount === '' || /^\d*\.?\d*$/.test(newAmount)) { dispatch({ type: actionTypes.UPDATE_BALANCE, payload: { - newValue: e.target.value, + newValue: newAmount, }, }) } } + const error = useMemo(() => { + if (!state.balance) return true + let balance = parseFloat(state.balance) + return balance < 100 || balance > parseFloat(maxBalance) - 0.5 + }, [state, maxBalance]) return ( - theme.palette.text.primary, - }} - > - Prefund Amount: - - } - endAdornment={ - error ? ( - - - - - - ) : ( - - - - + <> + theme.palette.text.primary, + }} + > + Prefund Amount: - ) - } - {...rest} - /> + } + endAdornment={ + error ? ( + + + + + + ) : ( + + + + + + ) + } + {...rest} + /> + {error && ( + + {parseFloat(state.balance) < 100 || !state.balance || state.balance === '0' + ? 'Balance must be at least 100' + : `Balance cannot exceed ${parseFloat(maxBalance) - 0.5}`} + + )} + ) } export default Input +//8 279.81669975 diff --git a/src/components/Partners/UpdatedSelectComponent.tsx b/src/components/Partners/UpdatedSelectComponent.tsx index 85eabfcf..19e6e9ef 100644 --- a/src/components/Partners/UpdatedSelectComponent.tsx +++ b/src/components/Partners/UpdatedSelectComponent.tsx @@ -1,14 +1,23 @@ import { Autocomplete, TextField, Typography } from '@mui/material' -import React, { useMemo } from 'react' +import React, { useMemo, useState } from 'react' const UpdatedSelectComponent = React.memo( ({ editing = true, supplierState, dispatchSupplierState, actionTypes }) => { + const [value, setValue] = useState(null) + const [inputValue, setInputValue] = useState('') + const handleChange = (event, newValue) => { if (newValue) { addService(newValue) + setValue(null) + setInputValue('') } } + const handleInputChange = (event, newInputValue) => { + setInputValue(newInputValue) + } + const addService = service => { if ( !supplierState.stepsConfig[supplierState.step].services.find( @@ -60,6 +69,10 @@ const UpdatedSelectComponent = React.memo( ( )} - onChange={handleChange} filterOptions={(options, { inputValue }) => options.filter(option => option.toLowerCase().includes(inputValue.toLowerCase()), @@ -106,6 +118,13 @@ const UpdatedSelectComponent = React.memo( borderColor: theme => theme.palette.card.border, borderRadius: '12px', }, + '& input': { + color: 'white', + }, + '& input::placeholder': { + color: 'white', + opacity: 1, + }, }, '& .MuiAutocomplete-input': { padding: '0 !important', diff --git a/src/helpers/partnerConfigurationContext.tsx b/src/helpers/partnerConfigurationContext.tsx index 9ab79143..03bb109d 100644 --- a/src/helpers/partnerConfigurationContext.tsx +++ b/src/helpers/partnerConfigurationContext.tsx @@ -8,7 +8,7 @@ const initialState = { stepsConfig: [ { step: 0, - title: 'Messenger configuration', + title: 'Create Messenger Account', services: [], paragraph: 'This wizard will help you create and activate your Camino Messenger Account with Camino Messenger address. Once set up, your Camino Messenger address will be displayed on your partner detail page, enabling direct communication with other Camino Messenger users.', diff --git a/src/layout/PartnersLayout.tsx b/src/layout/PartnersLayout.tsx index 03ef92ab..a5d0bca8 100644 --- a/src/layout/PartnersLayout.tsx +++ b/src/layout/PartnersLayout.tsx @@ -1,11 +1,9 @@ -import { Box, Link, Toolbar, Typography } from '@mui/material' +import { Box, Button, Link, Toolbar, Typography } from '@mui/material' import { Paper } from '@mui/material' -import { ethers } from 'ethers' import React, { useEffect } from 'react' import { Navigate, Outlet, useNavigate, useParams } from 'react-router' import store from 'wallet/store' -import CMAccount from '../helpers/CMAccountManagerModule#CMAccount.json' import { PartnerConfigurationProvider } from '../helpers/partnerConfigurationContext' import { SmartContractProvider } from '../helpers/useSmartContract' import { useAppSelector } from '../hooks/reduxHooks' @@ -15,6 +13,15 @@ import { getActiveNetwork } from '../redux/slices/network' import Links from '../views/settings/Links' const ClaimProfile = () => { + const generateEmail = () => { + const subject = 'Claim a Partner' + const body = `This is to claim a Partner record and associate it to the wallet with C-Chain address .Please add your name, contact details, the Partner name, and attach any evidence of your affiliation with the Partner.` + + const mailtoLink = `mailto:${emailAddress}?subject=${encodeURIComponent( + subject, + )}&body=${encodeURIComponent(body)}` + window.location.href = mailtoLink + } const emailAddress = 'foundation@camino.network' return ( { > Claim a profile - To manage and configure your Camino Messenger, you need to claim an account. + To manage and configure your Camino Messenger, you need to claim a Partner record + first. - Contact the Camino Network Foundation via{' '} - - email - {' '} + Contact the Camino Network Foundation via email{' '} to proceed. + ) } -// Function to create a contract instance -function getPartnerContract(address, provider) { - return new ethers.Contract(address, CMAccount, provider) -} - -// Main function to get services from multiple partner contracts -// async function getPartnerServices(partnerAddresses: string[], providerUrl: string) { -// const provider = new ethers.JsonRpcProvider(providerUrl) -// for (const address of partnerAddresses) { -// const contract = getPartnerContract(address, provider) -// try { -// const result = await contract.getSupportedServices() -// console.log(result) -// } catch (error) {} -// } -// } - const PartnersLayout = () => { const path = window.location.pathname const { data, isLoading } = useIsPartnerQuery({ diff --git a/src/views/partners/ConfigurDistrubitor.tsx b/src/views/partners/ConfigurDistrubitor.tsx index 0d5750d4..0c7dd084 100644 --- a/src/views/partners/ConfigurDistrubitor.tsx +++ b/src/views/partners/ConfigurDistrubitor.tsx @@ -25,7 +25,9 @@ import { } from '../../helpers/partnerConfigurationContext' import { usePartnerConfig } from '../../helpers/usePartnerConfig' import { useSmartContract } from '../../helpers/useSmartContract' +import { useAppDispatch } from '../../hooks/reduxHooks' import { useFetchPartnerDataQuery } from '../../redux/services/partners' +import { updateNotificationStatus } from '../../redux/slices/app-config' import { Configuration } from './Configuration' function ServiceChangesPreview({ added, removed }) { @@ -176,12 +178,6 @@ export const BasicWantedServices = () => { - - - ) } @@ -246,6 +242,7 @@ const ConfigurDistrubitor = () => { setRemoved(removed) }, [compareServices]) + const appDispatch = useAppDispatch() async function confirmEditing() { setLoading(true) await removeWantedServices(removed.map(elem => elem.name)) @@ -255,6 +252,12 @@ const ConfigurDistrubitor = () => { type: actionTypes.UPDATE_WANTED_SERVICES, payload: { wantedServices: res }, }) + appDispatch( + updateNotificationStatus({ + message: 'Services configured successfully', + severity: 'success', + }), + ) setAdded([]) setRemoved([]) setLoading(false) @@ -325,7 +328,7 @@ const ConfigurDistrubitor = () => { setEditing(true) }} > - Edit configuration + Configure Services ) : ( <> @@ -336,7 +339,7 @@ const ConfigurDistrubitor = () => { cancelEditing() }} > - Cancel Editing + Cancel { confirmEditing() }} > - Confirm Editing + Save Changes )} - - - ) } diff --git a/src/views/partners/ConfigurSupplier.tsx b/src/views/partners/ConfigurSupplier.tsx index 4c8ba5dc..8ca68ed9 100644 --- a/src/views/partners/ConfigurSupplier.tsx +++ b/src/views/partners/ConfigurSupplier.tsx @@ -25,7 +25,9 @@ import { } from '../../helpers/partnerConfigurationContext' import { usePartnerConfig } from '../../helpers/usePartnerConfig' import { useSmartContract } from '../../helpers/useSmartContract' +import { useAppDispatch } from '../../hooks/reduxHooks' import { useFetchPartnerDataQuery } from '../../redux/services/partners' +import { updateNotificationStatus } from '../../redux/slices/app-config' import { Configuration } from './Configuration' function ServiceChangesPreview({ added, updated, removed }) { @@ -320,12 +322,6 @@ export const BasicSupportedServices = () => { - - - ) } @@ -434,6 +430,8 @@ const ConfigurSupplier = () => { setIsFetching(false) } + const appDispatch = useAppDispatch() + async function confirmEditing() { setLoading(true) if (removed.length > 0) await removeServices(removed) @@ -450,6 +448,12 @@ const ConfigurSupplier = () => { type: actionTypes.RESET_STATE, payload: { initialState: { ...state, step: 1 } }, }) + appDispatch( + updateNotificationStatus({ + message: 'Services configured successfully', + severity: 'success', + }), + ) setAdded([]) setRemoved([]) setUpdated([]) @@ -523,7 +527,7 @@ const ConfigurSupplier = () => { setEditing(true) }} > - Edit configuration + Configure Services ) : ( <> @@ -534,7 +538,7 @@ const ConfigurSupplier = () => { cancelEditing() }} > - Cancel Editing + Cancel { confirmEditing() }} > - Confirm Editing + Save Changes )} - + ) diff --git a/src/views/partners/Configuration.tsx b/src/views/partners/Configuration.tsx index a6079b87..0b60567b 100644 --- a/src/views/partners/Configuration.tsx +++ b/src/views/partners/Configuration.tsx @@ -1,9 +1,11 @@ +import RefreshIcon from '@mui/icons-material/Refresh' import { Box, Button, Checkbox, Divider, FormControlLabel, + IconButton, InputAdornment, OutlinedInput, TextField, @@ -22,6 +24,8 @@ import { import { usePartnerConfig } from '../../helpers/usePartnerConfig' import { useSmartContract } from '../../helpers/useSmartContract' import useWalletBalance from '../../helpers/useWalletBalance' +import { useAppDispatch } from '../../hooks/reduxHooks' +import { updateNotificationStatus } from '../../redux/slices/app-config' import MyMessenger from './MyMessenger' const Content = () => { @@ -29,13 +33,19 @@ const Content = () => { const { state, dispatch } = usePartnerConfigurationContext() const [loading, setLoading] = useState(false) const partnerConfig = usePartnerConfig() - + const appDispatch = useAppDispatch() async function submit() { setLoading(true) await partnerConfig.CreateConfiguration(state) + appDispatch( + updateNotificationStatus({ + message: 'Messenger Account Created', + severity: 'success', + }), + ) setLoading(false) } - const { balance } = useWalletBalance() + const { balance, fetchBalance } = useWalletBalance() if (contractCMAccountAddress) return return ( @@ -51,15 +61,57 @@ const Content = () => { Messenger setup {state.stepsConfig[state.step].title} {state.stepsConfig[state.step].paragraph && ( - - {state.stepsConfig[state.step].paragraph} - + <> + + To create a Messenger Account you need to: + +
    +
  1. + + Be KYC-verified and fund the C-Chain address of your connected + Wallet with at least 100 CAM. + +
  2. +
  3. + + Create the Account in this page. + +
  4. +
  5. + + Configure Services that you offer to Partners. + +
  6. +
  7. + + Configure Services that you are looking for in Camino Network. + +
  8. +
  9. + + Manage the Bots associated to your Messenger Account. + +
  10. +
+ {balance !== '' && parseFloat(balance) < 100 && ( + + Refresh Balance + + + + + )} + )} {state.step === 0 && ( <> {balance !== '' && parseFloat(balance) < 100 && ( - + )} {!store.getters['Accounts/kycStatus'] && ( @@ -67,39 +119,39 @@ const Content = () => { )} - - - Create Configuration - - + + parseFloat(balance) - 0.5 || + !store.getters['Accounts/kycStatus'] + } + > + Create + + {state.step === 0 && ( )} - ) @@ -222,61 +274,6 @@ Configuration.Services = function Services({ }} > {service.name} - {!!!partnerID && ( - - {state.step === 1 && ( - - )} - - - )} {state.step === 1 && ( <> @@ -380,20 +377,29 @@ Configuration.Services = function Services({ Rack Rates } + disabled={disabled} control={ - theme.palette.secondary.main, + disabled + ? theme.palette.action.disabled + : theme.palette.secondary.main, '&.Mui-checked': { color: theme => - theme.palette.secondary.main, + disabled + ? theme.palette.action.disabled + : theme.palette.secondary.main, }, '&.MuiCheckbox-colorSecondary.Mui-checked': { color: theme => - theme.palette.secondary.main, + disabled + ? theme.palette.action + .disabled + : theme.palette.secondary + .main, }, }} checked={ @@ -414,20 +420,6 @@ Configuration.Services = function Services({ } /> - - - - - )} {state.stepsConfig[state.step].services[index].capabilities.map( @@ -475,33 +467,83 @@ Configuration.Services = function Services({ }} placeholder="Describe your capabilities..." /> - - - - - ) }, )} )} + {!!!partnerID && ( + + {state.step === 1 && ( + + )} + + + )} ))} ) } -Configuration.Infos = function Infos({ rackRates, information }) { +Configuration.Infos = function Infos({ + rackRates, + information, + infos, + offred, +}: { + rackRates?: string + information?: string + infos?: string[] + offred?: boolean +}) { return ( - - information - {information} - - - - rack rates - {rackRates} - + {offred && ( + + information + For every service, you can: +
    +
  • + + Set a fee for the caller, in CAM + +
  • +
  • + + Flag it as offering "rack" rates, or not. Rack rates are public, + not-negotiated rate on product available for consumption on the + Camino Network + +
  • +
  • + + Add capabilities, as free text to describe the service in detail, + e.g. destination, rate types, etc. + +
  • +
+
+ )} + {information && ( + + information + {information} + + )} + + {rackRates && ( + <> + + + rack rates + {rackRates} + + + )} + {infos && + infos.length > 0 && + infos.map((elem, index) => ( + + + + {elem} + + + ))}
) } diff --git a/src/views/partners/ManageBots.tsx b/src/views/partners/ManageBots.tsx index ebd21ec5..5a1666b1 100644 --- a/src/views/partners/ManageBots.tsx +++ b/src/views/partners/ManageBots.tsx @@ -4,12 +4,12 @@ import React, { useEffect, useState } from 'react' import { useParams } from 'react-router' import Alert from '../../components/Alert' import { usePartnerConfig } from '../../helpers/usePartnerConfig' +import { useAppDispatch } from '../../hooks/reduxHooks' import { useFetchPartnerDataQuery } from '../../redux/services/partners' +import { updateNotificationStatus } from '../../redux/slices/app-config' import { Configuration } from './Configuration' export const BasicManageBots = () => { - const [bots, setBots] = useState([]) - const [loading, setLoading] = useState(false) const { partnerID } = useParams() const { data: partner } = useFetchPartnerDataQuery({ companyName: partnerID, @@ -26,8 +26,7 @@ export const BasicManageBots = () => { Manage Bots - Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula - eget dolor. Aenean massa. Donec sociis natoque penatibus. + List in this page the addresses of all bots using this Messenger Account. {partner.bots && partner.bots.length > 0 && @@ -95,12 +94,6 @@ export const BasicManageBots = () => { ) })} - - - ) } @@ -115,6 +108,8 @@ const ManageBots = () => { setAddress(newAddress) setIsValidAddress(ethers.isAddress(newAddress)) } + const appDispatch = useAppDispatch() + const { addMessengerBot, getListOfBots, removeMessengerBot } = usePartnerConfig() async function fetchBots() { @@ -129,15 +124,29 @@ const ManageBots = () => { const handleAddBot = () => { if (isValidAddress) { setLoading(true) - addMessengerBot(address).then(() => { + addMessengerBot(address).then(async () => { setAddress('') - fetchBots() + await fetchBots() + appDispatch( + updateNotificationStatus({ + message: 'Bot added successfully', + severity: 'success', + }), + ) }) } } const handleRemoveBot = address => { setLoading(true) - removeMessengerBot(address).then(() => fetchBots()) + removeMessengerBot(address).then(async () => { + await fetchBots() + appDispatch( + updateNotificationStatus({ + message: 'Bot removed successfully', + severity: 'success', + }), + ) + }) } return ( { Manage Bots - Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Aenean commodo ligula - eget dolor. Aenean massa. Donec sociis natoque penatibus. + List in this page the addresses of all bots using this Messenger Account. {loading && ( @@ -232,17 +240,13 @@ const ManageBots = () => { /> )} @@ -660,7 +720,7 @@ const MyMessenger = () => { {!isEditMode ? ( ) : ( <> @@ -680,39 +740,22 @@ const MyMessenger = () => { {isLoading ? ( ) : ( - 'Confirm' + 'Save Changes' )} )} - {state.stepsConfig[1]?.services.length == 0 && ( - - )} - {state.stepsConfig[2]?.services.length == 0 && ( - - )} - {bots.length == 0 && ( - - )} @@ -751,7 +794,7 @@ const MyMessenger = () => { theme.palette.mode === 'dark' ? '#020617' : '#F1F5F9', }} > - + diff --git a/src/views/partners/Partner.tsx b/src/views/partners/Partner.tsx index 640b9dd6..524fdd30 100644 --- a/src/views/partners/Partner.tsx +++ b/src/views/partners/Partner.tsx @@ -5,6 +5,7 @@ import Icon from '@mdi/react' import { ContentCopy } from '@mui/icons-material' import React, { useCallback, useEffect, useMemo, useState } from 'react' import { Link, useNavigate, useParams } from 'react-router-dom' +import store from 'wallet/store' import PartnerBusinessFields from '../../components/Partners/PartnerBusinessFields' import PartnerFlag from '../../components/Partners/PartnerFlag' import PartnerLogo from '../../components/Partners/PartnerLogo' @@ -13,7 +14,7 @@ import { usePartnerConfig } from '../../helpers/usePartnerConfig' import { useSmartContract } from '../../helpers/useSmartContract' import { useAppSelector } from '../../hooks/reduxHooks' import useWallet from '../../hooks/useWallet' -import { useFetchPartnerDataQuery } from '../../redux/services/partners' +import { useFetchPartnerDataQuery, useIsPartnerQuery } from '../../redux/services/partners' import { selectValidators } from '../../redux/slices/app-config' import { displayFirstPartLongString, displaySecondPartLongString } from '../../utils/display-utils' @@ -61,17 +62,44 @@ function getServiceName(fullName: unknown): string { return parts[parts.length - 1] || '' } -const Widget = ({ supportedServices, CMAccountAddress, wantedServices, supportedCurrencies }) => { +const Widget = ({ + supportedServices, + CMAccountAddress, + wantedServices, + supportedCurrencies, + partner, +}) => { + const { data } = useIsPartnerQuery({ + cChainAddress: store?.state?.activeWallet?.ethAddress + ? '0x' + store?.state?.activeWallet?.ethAddress + : '', + }) const auth = useAppSelector(state => state.appConfig.isAuth) const value = useSmartContract() const navigate = useNavigate() const [match, setMatch] = useState(false) const wantedServiceTypes = useMemo(() => { - return [...new Set(wantedServices.map(elem => getServiceType(elem?.name ? elem.name : '')))] + return [ + ...new Set( + wantedServices.map(elem => { + const serviceName = elem?.name ? elem.name : '' + const serviceType = getServiceType(serviceName) + return serviceType.endsWith('Service') ? serviceType.slice(0, -7) : serviceType + }), + ), + ] }, []) const supportedServiceTypes = useMemo(() => { return [ - ...new Set(supportedServices.map(elem => getServiceType(elem?.name ? elem.name : ''))), + ...new Set( + supportedServices.map(elem => { + const serviceName = elem?.name ? elem.name : '' + const serviceType = getServiceType(serviceName) + return serviceType.endsWith('Service') + ? serviceType.slice(0, -7) // Remove 'Service' from the end + : serviceType + }), + ), ] }, []) @@ -102,8 +130,9 @@ const Widget = ({ supportedServices, CMAccountAddress, wantedServices, supported ) { const supportedResult = await partnerConf.getSupportedServices() const wantedResult = await partnerConf.getWantedServices() + const result = checkMatch({ - supportedResult: supportedResult[0], + supportedResult: supportedResult && supportedResult[0] ? supportedResult[0] : [], wantedResult, supportedServices, wantedServices, @@ -113,6 +142,63 @@ const Widget = ({ supportedServices, CMAccountAddress, wantedServices, supported } }, [partnerConf, auth]) + const generateEmail = () => { + let partnerWanetd = [ + ...new Set( + partner.wantedServices.map(elem => { + const serviceName = elem?.name ? elem.name : '' + const serviceType = getServiceType(serviceName) + return serviceType.endsWith('Service') ? serviceType.slice(0, -7) : serviceType + }), + ), + ].join(', ') + let partnerSupported = [ + ...new Set( + partner.supportedServices.map(elem => { + const serviceName = elem?.name ? elem.name : '' + const serviceType = getServiceType(serviceName) + return serviceType.endsWith('Service') ? serviceType.slice(0, -7) : serviceType + }), + ), + ].join(', ') + let otherPartnerAccept = [] + if (partner.supportedCurrencies && supportedCurrencies.isCam) otherPartnerAccept.push('CAM') + if (partner.supportedCurrencies && supportedCurrencies.offChainPaymentSupported) + otherPartnerAccept.push('offChainPaymentSupported') + let myPartnerAccept = [] + if (data.supportedCurrencies && data.supportedCurrencies.isCam) myPartnerAccept.push('CAM') + if (data.supportedCurrencies && data.supportedCurrencies.offChainPaymentSupported) + myPartnerAccept.push('offChainPaymentSupported') + const subject = 'Connect on Camino Messenger' + const body = ` + Dear Sirs, + + we are both on Camino Messenger. + + You: ${partner.attributes.companyName} + Offer: ${partnerSupported} + Want: ${partnerWanetd} + Accept: ${otherPartnerAccept.join(', ')} + Messenger Address: ${partner.contractAddress} + + We: ${data.attributes.companyName} + Offer: ${supportedServiceTypes} + Want: ${wantedServiceTypes} + Accept: ${myPartnerAccept} + Messenger Address: ${value?.contractCMAccountAddress} + + Please get in touch at your convenience to discuss a connection on Camino Messenger. + + Best Regards + ` + + const mailtoLink = `mailto:${ + partner.attributes.contactEmail + }?cc=foundation@camino.network&subject=${encodeURIComponent( + subject, + )}&body=${encodeURIComponent(body)}` + window.location.href = mailtoLink + } useEffect(() => { matchingPartners() }, []) @@ -178,7 +264,7 @@ const Widget = ({ supportedServices, CMAccountAddress, wantedServices, supported {supportedCurrencies?.isCam && (
  • - Cam + CAM
  • )} @@ -199,9 +285,13 @@ const Widget = ({ supportedServices, CMAccountAddress, wantedServices, supported )} ) : ( - - None. - +
      +
    • + + None. + +
    • +
    )} @@ -227,10 +317,11 @@ const Widget = ({ supportedServices, CMAccountAddress, wantedServices, supported {match && ( - Its a match + It’s a match - This distributor seems to fit perfectly for you supplier company model. You - can connect to each other via the messenger + Offered and/or wanted services by this Partner combine with what you look + for and offer on Camino Network: you can easily connect with them via the + Messenger. )} @@ -256,7 +347,7 @@ const Widget = ({ supportedServices, CMAccountAddress, wantedServices, supported CMAccountAddress.toLocaleLowerCase() !== value?.contractCMAccountAddress.toLocaleLowerCase() && (