diff --git a/packages/concordia-app/public/locales/en/translation.json b/packages/concordia-app/public/locales/en/translation.json index c4834b6..d466c8b 100644 --- a/packages/concordia-app/public/locales/en/translation.json +++ b/packages/concordia-app/public/locales/en/translation.json @@ -6,13 +6,25 @@ "post.list.row.post.id": "#{{id}}", "register.card.header": "Sign Up", "register.form.button.back": "Back", - "register.form.button.guest": "Continue as guest", - "register.form.button.submit": "Sign Up", - "register.form.error.message.header": "Form contains errors", - "register.form.error.username.taken.message": "The username {{username}} is already taken.", "register.form.header.already.member.message": "There is already an account for this address.\nIf you want to create another account please change your address.", - "register.form.username.field.label": "Username", - "register.form.username.field.placeholder": "Username", + "register.form.personal.information.step.button.skip": "Skip for now", + "register.form.personal.information.step.button.submit": "Submit", + "register.form.personal.information.step.error.invalid.profile.picture.url.message": "The profile picture URL provided is not valid.", + "register.form.personal.information.step.error.message.header": "Form contains errors", + "register.form.personal.information.step.location.field.label": "Location", + "register.form.personal.information.step.location.field.placeholder": "Location", + "register.form.personal.information.step.profile.picture.field.label": "Profile picture URL", + "register.form.personal.information.step.profile.picture.field.placeholder": "URL", + "register.form.profile.information.step.description": "Give a hint about who you are", + "register.form.profile.information.step.title": "Profile Information", + "register.form.sign.up.step.button.guest": "Continue as guest", + "register.form.sign.up.step.button.submit": "Sign Up", + "register.form.sign.up.step.description": "Create a Concordia account", + "register.form.sign.up.step.error.message.header": "Form contains errors", + "register.form.sign.up.step.error.username.taken.message": "The username {{username}} is already taken.", + "register.form.sign.up.step.title": "Sign Up", + "register.form.sign.up.step.username.field.label": "Username", + "register.form.sign.up.step.username.field.placeholder": "Username", "register.p.account.address": "Account address:", "topbar.button.create.topic": "Create topic", "topbar.button.profile": "Profile", diff --git a/packages/concordia-app/src/App.jsx b/packages/concordia-app/src/App.jsx index 7ed70c0..d3b3e3e 100644 --- a/packages/concordia-app/src/App.jsx +++ b/packages/concordia-app/src/App.jsx @@ -19,6 +19,7 @@ const App = ({ store }) => ( ); App.propTypes = { + // eslint-disable-next-line react/forbid-prop-types store: PropTypes.object.isRequired, }; diff --git a/packages/concordia-app/src/constants/RegisterSteps.js b/packages/concordia-app/src/constants/RegisterSteps.js new file mode 100644 index 0000000..3e39afe --- /dev/null +++ b/packages/concordia-app/src/constants/RegisterSteps.js @@ -0,0 +1,2 @@ +export const REGISTER_STEP_SIGNUP = 'signup'; +export const REGISTER_STEP_PROFILE_INFORMATION = 'profile-information'; diff --git a/packages/concordia-app/src/constants/TransactionStatus.js b/packages/concordia-app/src/constants/TransactionStatus.js new file mode 100644 index 0000000..3ca1dca --- /dev/null +++ b/packages/concordia-app/src/constants/TransactionStatus.js @@ -0,0 +1,2 @@ +export const TRANSACTION_SUCCESS = 'success'; +export const TRANSACTION_ERROR = 'error'; diff --git a/packages/concordia-app/src/constants/UserDatabaseKeys.js b/packages/concordia-app/src/constants/UserDatabaseKeys.js new file mode 100644 index 0000000..91b7bd7 --- /dev/null +++ b/packages/concordia-app/src/constants/UserDatabaseKeys.js @@ -0,0 +1,2 @@ +export const PROFILE_PICTURE = 'profile_picture'; +export const LOCATION = 'location'; diff --git a/packages/concordia-app/src/redux/sagas/orbitSaga.js b/packages/concordia-app/src/redux/sagas/orbitSaga.js index 543a80a..749f498 100644 --- a/packages/concordia-app/src/redux/sagas/orbitSaga.js +++ b/packages/concordia-app/src/redux/sagas/orbitSaga.js @@ -10,7 +10,8 @@ import { EthereumContractIdentityProvider } from '@ezerous/eth-identity-provider function* initOrbitDatabases(action) { const { account, breeze } = action; - yield put(breezeActions.orbit.orbitInit(breeze, account + EthereumContractIdentityProvider.contractAddress)); // same as breeze.initOrbit(account); + // same as breeze.initOrbit(account); + yield put(breezeActions.orbit.orbitInit(breeze, account + EthereumContractIdentityProvider.contractAddress)); } function* orbitSaga() { diff --git a/packages/concordia-app/src/utils/urlUtils.js b/packages/concordia-app/src/utils/urlUtils.js new file mode 100644 index 0000000..84351d5 --- /dev/null +++ b/packages/concordia-app/src/utils/urlUtils.js @@ -0,0 +1,12 @@ +const checkUrlValid = (url) => { + const pattern = new RegExp('^(https?:\\/\\/)?' // protocol + + '((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' // domain name + + '((\\d{1,3}\\.){3}\\d{1,3}))' // OR ip (v4) address + + '(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' // port and path + + '(\\?[;&a-z\\d%_.~+=-]*)?' // query string + + '(\\#[-a-z\\d_]*)?$', 'i'); // fragment locator + + return !!pattern.test(url); +}; + +export default checkUrlValid; diff --git a/packages/concordia-app/src/views/Register/PersonalInformationStep/index.jsx b/packages/concordia-app/src/views/Register/PersonalInformationStep/index.jsx new file mode 100644 index 0000000..fb72557 --- /dev/null +++ b/packages/concordia-app/src/views/Register/PersonalInformationStep/index.jsx @@ -0,0 +1,175 @@ +import React, { + useCallback, useEffect, useMemo, useState, +} from 'react'; +import { + Button, Card, Form, Image, Input, Message, +} from 'semantic-ui-react'; +import { useTranslation } from 'react-i18next'; +import PropTypes from 'prop-types'; +import { useHistory } from 'react-router'; +import checkUrlValid from '../../../utils/urlUtils'; +import { breeze } from '../../../redux/store'; +import './styles.css'; +import { USER_DATABASE } from '../../../constants/OrbitDatabases'; +import { LOCATION, PROFILE_PICTURE } from '../../../constants/UserDatabaseKeys'; + +const { orbit: { stores } } = breeze; + +const PersonalInformationStep = (props) => { + const { pushNextStep } = props; + const [profilePictureInput, setProfilePictureInput] = useState(''); + const [profilePictureUrlValid, setProfilePictureUrlValid] = useState(true); + const [locationInput, setLocationInput] = useState(''); + const [error, setError] = useState(false); + const [errorMessages, setErrorMessages] = useState([]); + const history = useHistory(); + const { t } = useTranslation(); + + useEffect(() => { + let formHasError = false; + const formErrors = []; + + if (!profilePictureUrlValid) { + formHasError = true; + formErrors.push(t('register.form.personal.information.step.error.invalid.profile.picture.url.message')); + } + + setError(formHasError); + setErrorMessages(formErrors); + }, [profilePictureUrlValid, t]); + + const handleInputChange = useCallback((event, { name, value }) => { + if (name === 'profilePictureInput') { + setProfilePictureInput(value); + + if (value.length > 0) { + setProfilePictureUrlValid(checkUrlValid(value)); + } else { + setProfilePictureUrlValid(true); + } + } + + if (name === 'locationInput') { + setLocationInput(value); + } + }, []); + + const profilePicture = useMemo(() => (profilePictureInput.length > 0 && profilePictureUrlValid + ? ( +
+ +
+ ) + : null + ), [profilePictureInput, profilePictureUrlValid]); + + const handleSubmit = useCallback(() => { + if (error) { + return; + } + + const keyValuesToStore = []; + + if (profilePictureInput.length > 0) { + keyValuesToStore.push({ + key: PROFILE_PICTURE, + value: profilePictureInput, + }); + } + + if (locationInput.length > 0) { + keyValuesToStore.push({ + key: LOCATION, + value: locationInput, + }); + } + + if (keyValuesToStore.length > 0) { + const userDb = Object.values(stores).find((store) => store.dbname === USER_DATABASE); + + keyValuesToStore + .reduce((acc, keyValueToStore) => acc + .then(() => userDb + .put(keyValueToStore.key, keyValueToStore.value, { pin: true })), + Promise.resolve()) + .then(() => pushNextStep()) + .catch((reason) => { + console.log(reason); + }); + } + }, [error, locationInput, profilePictureInput, pushNextStep]); + + const goToHomePage = () => history.push('/'); + + return ( + <> + + +
+ + + + + {profilePicture} + + + + +
+
+
+ {error === true && ( + + {errorMessages + .map((errorMessage) => ( + + ))} + + )} + +