github.com/resonatecoop/id@v1.1.0-43/frontend/src/components/forms/emailUpdate.js (about) 1 /* global fetch */ 2 3 const html = require('choo/html') 4 const Component = require('choo/component') 5 const Form = require('./generic') 6 7 const isEqual = require('is-equal-shallow') 8 const logger = require('nanologger') 9 const log = logger('form:updateProfile') 10 11 const isEmpty = require('validator/lib/isEmpty') 12 const isEmail = require('validator/lib/isEmail') 13 const validateFormdata = require('validate-formdata') 14 const nanostate = require('nanostate') 15 16 // EMailUpdateForm ... 17 class EmailUpdateForm extends Component { 18 constructor (id, state, emit) { 19 super(id) 20 21 this.emit = emit 22 this.state = state 23 24 this.local = state.components[id] = Object.create({ 25 machine: nanostate.parallel({ 26 form: nanostate('idle', { 27 idle: { submit: 'submitted' }, 28 submitted: { valid: 'data', invalid: 'error' }, 29 data: { reset: 'idle', submit: 'submitted' }, 30 error: { reset: 'idle', submit: 'submitted', invalid: 'error' } 31 }), 32 request: nanostate('idle', { 33 idle: { start: 'loading' }, 34 loading: { resolve: 'data', reject: 'error' }, 35 data: { start: 'loading' }, 36 error: { start: 'loading', stop: 'idle' } 37 }), 38 loader: nanostate('off', { 39 on: { toggle: 'off' }, 40 off: { toggle: 'on' } 41 }) 42 }) 43 }) 44 45 this.local.error = {} 46 47 this.local.machine.on('form:reset', () => { 48 this.validator = validateFormdata() 49 this.local.form = this.validator.state 50 }) 51 52 this.local.machine.on('request:start', () => { 53 this.loaderTimeout = setTimeout(() => { 54 this.local.machine.emit('loader:toggle') 55 }, 300) 56 }) 57 58 this.local.machine.on('request:reject', () => { 59 this.emit('notify', { type: 'error', message: this.local.error.message || 'Something went wrong' }) 60 61 clearTimeout(this.loaderTimeout) 62 }) 63 64 this.local.machine.on('request:resolve', () => { 65 clearTimeout(this.loaderTimeout) 66 }) 67 68 this.local.machine.on('form:valid', async () => { 69 log.info('Form is valid') 70 71 try { 72 this.local.machine.emit('request:start') 73 74 let response = await fetch('') 75 76 const csrfToken = response.headers.get('X-CSRF-Token') 77 78 response = await fetch('', { 79 method: 'PUT', 80 headers: { 81 Accept: 'application/json', 82 'X-CSRF-Token': csrfToken 83 }, 84 redirect: 'follow', 85 body: new URLSearchParams({ 86 email: this.local.data.email || '', 87 password: this.local.data.password 88 }) 89 }) 90 91 const status = response.status 92 const contentType = response.headers.get('content-type') 93 94 if (status >= 400 && contentType && contentType.indexOf('application/json') !== -1) { 95 const { error } = await response.json() 96 this.local.error.message = error 97 this.local.machine.emit('request:reject') 98 } else { 99 this.local.machine.emit('request:resolve') 100 101 emit('notify', { 102 timeout: 3000, 103 type: 'info', 104 message: 'You have been logged out.' 105 }) 106 setTimeout(() => { 107 window.location.reload() 108 }, 3000) 109 } 110 } catch (err) { 111 this.local.machine.emit('request:reject') 112 console.log(err) 113 } 114 }) 115 116 this.local.machine.on('form:invalid', () => { 117 log.info('Form is invalid') 118 119 const invalidInput = document.querySelector('.invalid') 120 121 if (invalidInput) { 122 invalidInput.focus({ preventScroll: false }) // focus to first invalid input 123 } 124 }) 125 126 this.local.machine.on('form:submit', () => { 127 log.info('Form has been submitted') 128 129 const form = this.element.querySelector('form') 130 131 for (const field of form.elements) { 132 const isRequired = field.required 133 const name = field.name || '' 134 const value = field.value || '' 135 136 if (isRequired) { 137 this.validator.validate(name, value) 138 } 139 } 140 141 this.rerender() 142 143 this.local.machine.emit(`form:${this.local.form.valid ? 'valid' : 'invalid'}`) 144 }) 145 146 this.validator = validateFormdata() 147 this.local.form = this.validator.state 148 } 149 150 createElement (props = {}) { 151 this.local.data = this.local.data || props.data 152 153 const values = this.local.form.values 154 155 for (const [key, value] of Object.entries(this.local.data)) { 156 values[key] = value 157 } 158 159 return html` 160 <div class="flex flex-column flex-auto"> 161 ${this.state.cache(Form, 'account-form-update').render({ 162 id: 'account-update-email-form', 163 method: 'POST', 164 action: '', 165 buttonText: 'Update my email', 166 validate: (props) => { 167 this.local.data[props.name] = props.value 168 this.validator.validate(props.name, props.value) 169 this.rerender() 170 }, 171 form: this.local.form || { 172 changed: false, 173 valid: true, 174 pristine: {}, 175 required: {}, 176 values: {}, 177 errors: {} 178 }, 179 submit: (data) => { 180 this.local.machine.emit('form:submit') 181 }, 182 fields: [ 183 { 184 type: 'email', 185 placeholder: 'E-mail' 186 }, 187 { 188 type: 'password', 189 name: 'password', 190 id: 'password_new_email', 191 placeholder: 'Current password' 192 } 193 ] 194 })} 195 </div> 196 ` 197 } 198 199 load () { 200 this.validator.field('email', (data) => { 201 if (isEmpty(data)) return new Error('Email is required') 202 if (!isEmail(data)) return new Error('Email is invalid') 203 }) 204 this.validator.field('password', (data) => { 205 if (isEmpty(data)) return new Error('Password is required') 206 }) 207 } 208 209 update (props) { 210 if (!isEqual(props.data, this.local.data)) { 211 this.local.data = props.data 212 return true 213 } 214 return false 215 } 216 } 217 218 module.exports = EmailUpdateForm