user editing in jsx

This commit is contained in:
Connor Turland 2018-03-05 23:27:38 -05:00
parent 0220874590
commit d765487635
19 changed files with 326 additions and 275 deletions

View file

@ -4,18 +4,18 @@ function apiProxyMiddleware (req, res, next) {
if (!(req.xhr || req.originalUrl.indexOf('.json') > -1 || req.method !== 'GET')) { if (!(req.xhr || req.originalUrl.indexOf('.json') > -1 || req.method !== 'GET')) {
return next() return next()
} }
console.log('xhr request', req.originalUrl)
const method = req.method.toLowerCase() const method = req.method.toLowerCase()
req.pipe( req.pipe(
request[method](process.env.API + req.originalUrl, { request[method](process.env.API + req.originalUrl, {
headers: { headers: {
...req.headers, ...req.headers,
host: process.env.API cookie: `_Metamaps_session=${req.cookies._Metamaps_session}`,
host: 'localhost:3001'
}, },
followRedirect: false followRedirect: false
}) })
) )
.on('error', console.log) .on('error', (err) => console.log(err))
.pipe(res) .pipe(res)
} }

34
package-lock.json generated
View file

@ -67,9 +67,7 @@
} }
}, },
"action-cable-node": { "action-cable-node": {
"version": "1.2.2", "version": "github:Connoropolous/action-cable-node#346d691343251b4148ccc0607f87728d4d97f2cd"
"resolved": "https://registry.npmjs.org/action-cable-node/-/action-cable-node-1.2.2.tgz",
"integrity": "sha1-KwXl/iR+qqRXfwct2lDOCiEYOS0="
}, },
"after": { "after": {
"version": "0.8.1", "version": "0.8.1",
@ -1909,7 +1907,7 @@
"content-type": { "content-type": {
"version": "1.0.4", "version": "1.0.4",
"resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz",
"integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", "integrity": "sha1-4TjMdeBAxyexlm/l5fjJruJW/js=",
"dev": true "dev": true
}, },
"content-type-parser": { "content-type-parser": {
@ -1926,14 +1924,21 @@
"cookie": { "cookie": {
"version": "0.3.1", "version": "0.3.1",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz",
"integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=", "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s="
"dev": true },
"cookie-parser": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/cookie-parser/-/cookie-parser-1.4.3.tgz",
"integrity": "sha1-D+MfoZ0AC5X0qt8fU/3CuKIDuqU=",
"requires": {
"cookie": "0.3.1",
"cookie-signature": "1.0.6"
}
}, },
"cookie-signature": { "cookie-signature": {
"version": "1.0.6", "version": "1.0.6",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz",
"integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=", "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw="
"dev": true
}, },
"copy-descriptor": { "copy-descriptor": {
"version": "0.1.1", "version": "0.1.1",
@ -5479,6 +5484,11 @@
"jquery": "3.2.1" "jquery": "3.2.1"
} }
}, },
"js-cookie": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-2.2.0.tgz",
"integrity": "sha1-Gywnmm7s44ChIWi5JIUmWzWx7/s="
},
"js-tokens": { "js-tokens": {
"version": "3.0.2", "version": "3.0.2",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz",
@ -6297,7 +6307,7 @@
"mime": { "mime": {
"version": "1.4.1", "version": "1.4.1",
"resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz",
"integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", "integrity": "sha1-Eh+evEnjdm8xGnbh+hyAA8SwOqY=",
"dev": true "dev": true
}, },
"mime-db": { "mime-db": {
@ -8276,7 +8286,7 @@
"send": { "send": {
"version": "0.16.1", "version": "0.16.1",
"resolved": "https://registry.npmjs.org/send/-/send-0.16.1.tgz", "resolved": "https://registry.npmjs.org/send/-/send-0.16.1.tgz",
"integrity": "sha512-ElCLJdJIKPk6ux/Hocwhk7NFHpI3pVm/IZOYWqUmoxcgeyM+MpxHHKhb8QmlJDX1pU6WrgaHBkVNm73Sv7uc2A==", "integrity": "sha1-pw4coh0TgsEdDZ9iMd6ygQgNerM=",
"dev": true, "dev": true,
"requires": { "requires": {
"debug": "2.6.9", "debug": "2.6.9",
@ -8320,7 +8330,7 @@
"serve-static": { "serve-static": {
"version": "1.13.1", "version": "1.13.1",
"resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.1.tgz", "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.1.tgz",
"integrity": "sha512-hSMUZrsPa/I09VYFJwa627JJkNs0NrfL1Uzuup+GqHfToR2KcsXFymXSV90hoyw3M+msjFuQly+YzIH/q0MGlQ==", "integrity": "sha1-TFfVNASnYdjy58HooYpH2/J4pxk=",
"dev": true, "dev": true,
"requires": { "requires": {
"encodeurl": "1.0.2", "encodeurl": "1.0.2",
@ -8368,7 +8378,7 @@
"setprototypeof": { "setprototypeof": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz",
"integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", "integrity": "sha1-0L2FU2iHtv58DYGMuWLZ2RxU5lY=",
"dev": true "dev": true
}, },
"sha.js": { "sha.js": {

View file

@ -36,6 +36,7 @@
"backbone": "1.3.3", "backbone": "1.3.3",
"clipboard-js": "0.3.5", "clipboard-js": "0.3.5",
"commonmark": "0.28.1", "commonmark": "0.28.1",
"cookie-parser": "^1.4.3",
"csv-parse": "1.2.1", "csv-parse": "1.2.1",
"emoji-mart": "1.0.1", "emoji-mart": "1.0.1",
"getscreenmedia": "4.0.1", "getscreenmedia": "4.0.1",

View file

@ -64,8 +64,7 @@
<![endif]--> <![endif]-->
</head> </head>
<!-- TODO: make 'authenticated' class dynamic --> <body class="unauthenticated">
<body class="authenticated">
<div class="main"> <div class="main">
<div id="app"></div> <div id="app"></div>
<div id="loading"></div> <div id="loading"></div>
@ -107,13 +106,6 @@
<script type="text/javascript"> <script type="text/javascript">
Metamaps.ServerData = Metamaps.ServerData || {} Metamaps.ServerData = Metamaps.ServerData || {}
Metamaps.ServerData.RAILS_ENV = 'development' Metamaps.ServerData.RAILS_ENV = 'development'
Metamaps.ServerData.ActiveMapper = {
id: 10,
name: 'Connor',
email: 'co@co.com',
admin: false,
image: 'https://metamaps-live.s3.amazonaws.com/users/images/555/629/996/sixtyfour/11835c3.png?1417298429'
}
Metamaps.ServerData['junto_spinner_darkgrey.gif'] = '/images/junto_spinner_darkgrey.gif' Metamaps.ServerData['junto_spinner_darkgrey.gif'] = '/images/junto_spinner_darkgrey.gif'
Metamaps.ServerData['user.png'] = '/images/user.png' Metamaps.ServerData['user.png'] = '/images/user.png'
Metamaps.ServerData['icons/wildcard.png'] = '/images/icons/wildcard.png' Metamaps.ServerData['icons/wildcard.png'] = '/images/icons/wildcard.png'

View file

@ -1,17 +1,3 @@
@import 'admin';
@import 'apps';
@import 'base';
@import 'clean';
@import 'emoji-mart-0.3.5';
@import 'feedback';
@import 'jquery-ui';
@import 'junto';
@import 'mapcard';
@import 'mobile';
@import 'notifications';
@import 'request_access';
@import 'search';
/* clear styles */ /* clear styles */
html, html,
@ -243,10 +229,6 @@ button.button.btn-no:hover {
color:#424242; color:#424242;
} }
.toHide {
display:none;
}
.forgotPassword { .forgotPassword {
height: 134px; height: 134px;
font-family: din-medium; font-family: din-medium;
@ -327,7 +309,6 @@ button.button.btn-no:hover {
position: absolute; position: absolute;
top: 88px; top: 88px;
left: -18px; left: -18px;
display: none;
} }
.userMenuArrow { .userMenuArrow {
border-color: transparent; border-color: transparent;
@ -436,7 +417,6 @@ button.button.btn-no:hover {
margin:5px 0px 0px 5px; margin:5px 0px 0px 5px;
} }
.changeName { .changeName {
display: none;
margin-top: 24px; margin-top: 24px;
} }
@ -3029,3 +3009,17 @@ script.data-gratipay-username {
font-size: 13px; font-size: 13px;
font-weight: bold; font-weight: bold;
} }
@import 'admin';
@import 'apps';
@import 'base';
@import 'clean';
@import 'emoji-mart-0.3.5';
@import 'feedback';
@import 'jquery-ui';
@import 'junto';
@import 'mapcard';
@import 'mobile';
@import 'notifications';
@import 'request_access';
@import 'search';

View file

@ -1,5 +1,6 @@
const path = require('path') const path = require('path')
const express = require('express') const express = require('express')
const cookieParser = require('cookie-parser')
const webpack = require('webpack') const webpack = require('webpack')
const socketio = require('socket.io') const socketio = require('socket.io')
const { createServer } = require('http') const { createServer } = require('http')
@ -13,6 +14,8 @@ const server = createServer(app)
const io = socketio(server) const io = socketio(server)
realtime(io) // sets up the socketio event listeners realtime(io) // sets up the socketio event listeners
app.use(cookieParser())
// serve the whole public folder as static files // serve the whole public folder as static files
app.use(express.static(path.join(__dirname, 'public'))) app.use(express.static(path.join(__dirname, 'public')))

View file

@ -1,112 +0,0 @@
/* global $, CanvasLoader */
const Account = {
init: function(serverData) {
Account.userIconUrl = serverData['user.png']
},
listenersInitialized: false,
userIconUrl: null,
initListeners: function() {
var self = Account
$('#user_image').change(self.showImagePreview)
self.listenersInitialized = true
},
toggleChangePicture: function() {
var self = Account
$('.userImageMenu').toggle()
if (!self.listenersInitialized) self.initListeners()
},
openChangePicture: function() {
var self = Account
$('.userImageMenu').show()
if (!self.listenersInitialized) self.initListeners()
},
closeChangePicture: function() {
$('.userImageMenu').hide()
},
showLoading: function() {
var loader = new CanvasLoader('accountPageLoading')
loader.setColor('#4FC059') // default is '#000000'
loader.setDiameter(28) // default is 40
loader.setDensity(41) // default is 40
loader.setRange(0.9) // default is 1.3
loader.show() // Hidden by default
$('#accountPageLoading').show()
},
showImagePreview: function() {
var file = $('#user_image')[0].files[0]
var reader = new window.FileReader()
reader.onload = function(e) {
var $canvas = $('<canvas>').attr({
width: 84,
height: 84
})
var context = $canvas[0].getContext('2d')
var imageObj = new window.Image()
imageObj.onload = function() {
$('.userImageDiv canvas').remove()
$('.userImageDiv img').hide()
var imgWidth = imageObj.width
var imgHeight = imageObj.height
var dimensionToMatch = imgWidth > imgHeight ? imgHeight : imgWidth
// draw cropped image
var nonZero = Math.abs(imgHeight - imgWidth) / 2
var sourceX = dimensionToMatch === imgWidth ? 0 : nonZero
var sourceY = dimensionToMatch === imgHeight ? 0 : nonZero
var sourceWidth = dimensionToMatch
var sourceHeight = dimensionToMatch
var destX = 0
var destY = 0
var destWidth = 84
var destHeight = 84
context.drawImage(imageObj, sourceX, sourceY, sourceWidth, sourceHeight, destX, destY, destWidth, destHeight)
$('.userImageDiv').prepend($canvas)
}
imageObj.src = reader.result
}
if (file) {
reader.readAsDataURL(file)
$('.userImageMenu').hide()
$('#remove_image').val('0')
}
},
removePicture: function() {
var self = Account
$('.userImageDiv canvas').remove()
$('.userImageDiv img').attr('src', self.userIconUrl).show()
$('.userImageMenu').hide()
var input = $('#user_image')
input.replaceWith(input.val('').clone(true))
$('#remove_image').val('1')
},
changeName: function() {
$('.accountName').hide()
$('.changeName').show()
},
showPass: function() {
$('.toHide').show()
$('.changePass').hide()
},
hidePass: function() {
$('.toHide').hide()
$('.changePass').show()
$('#current_password').val('')
$('#user_password').val('')
$('#user_password_confirmation').val('')
}
}
export default Account

View file

@ -1,10 +1,20 @@
function fetchWithCookies(url) {
return fetch(url, { credentials: 'same-origin' })
}
async function getMetacodes() { async function getMetacodes() {
const res = await fetch('/metacodes.json') const res = await fetchWithCookies('/metacodes.json')
const data = await res.json()
return data
}
async function getCurrentUser() {
const res = await fetchWithCookies('/users/current.json')
const data = await res.json() const data = await res.json()
return data return data
} }
module.exports = { module.exports = {
getMetacodes getMetacodes,
getCurrentUser
} }

View file

@ -1,4 +1,3 @@
import Account from './Account'
import Active from './Active' import Active from './Active'
import Admin from './Admin' import Admin from './Admin'
import AutoLayout from './AutoLayout' import AutoLayout from './AutoLayout'
@ -32,7 +31,6 @@ import Views from './Views'
import Visualize from './Visualize' import Visualize from './Visualize'
const Metamaps = window.Metamaps || {} const Metamaps = window.Metamaps || {}
Metamaps.Account = Account
Metamaps.Active = Active Metamaps.Active = Active
Metamaps.Admin = Admin Metamaps.Admin = Admin
Metamaps.AutoLayout = AutoLayout Metamaps.AutoLayout = AutoLayout
@ -91,6 +89,12 @@ document.addEventListener('DOMContentLoaded', async function() {
try { try {
const metacodes = await DataFetcher.getMetacodes() const metacodes = await DataFetcher.getMetacodes()
Metamaps.ServerData.Metacodes = metacodes Metamaps.ServerData.Metacodes = metacodes
const activeMapper = await DataFetcher.getCurrentUser()
if (activeMapper) {
Metamaps.ServerData.ActiveMapper = activeMapper
$('body').removeClass('unauthenticated').addClass('authenticated')
}
runInitFunctions(Metamaps.ServerData) runInitFunctions(Metamaps.ServerData)
} catch (e) { } catch (e) {
console.log(e) console.log(e)

View file

@ -1,5 +1,6 @@
import React, { Component } from 'react' import React, { Component } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { Link } from 'react-router'
import onClickOutsideAddon from 'react-onclickoutside' import onClickOutsideAddon from 'react-onclickoutside'
@ -22,15 +23,15 @@ class AccountMenu extends Component {
<ul> <ul>
<li className="accountListItem accountSettings"> <li className="accountListItem accountSettings">
<div className="accountIcon"></div> <div className="accountIcon"></div>
<a href={`/users/${currentUser.id}/edit`}>Settings</a> <Link to={`/users/${currentUser.id}/edit`}>Settings</Link>
</li> </li>
<li className="accountListItem accountAdmin"> <li className="accountListItem accountAdmin">
<div className="accountIcon"></div> <div className="accountIcon"></div>
<a href="/metacodes">Admin</a> <Link to="/metacodes">Admin</Link>
</li> </li>
<li className="accountListItem accountApps"> <li className="accountListItem accountApps">
<div className="accountIcon"></div> <div className="accountIcon"></div>
<a href="/oauth/authorized_applications">Apps</a> <Link to="/oauth/authorized_applications">Apps</Link>
</li> </li>
<li className="accountListItem accountInvite" onClick={onInviteClick}> <li className="accountListItem accountInvite" onClick={onInviteClick}>
<div className="accountIcon"></div> <div className="accountIcon"></div>

View file

@ -1,5 +1,6 @@
import React, { Component } from 'react' import React, { Component } from 'react'
import PropTypes from 'prop-types' import PropTypes from 'prop-types'
import { Link } from 'react-router'
import onClickOutsideAddon from 'react-onclickoutside' import onClickOutsideAddon from 'react-onclickoutside'
@ -43,7 +44,7 @@ class LoginForm extends Component {
</div> </div>
<div className="clearfloat"></div> <div className="clearfloat"></div>
<div className="accountForgotPass"> <div className="accountForgotPass">
<a href="/users/password/new">Forgot password?</a> <Link to="/users/password/new">Forgot password?</Link>
</div> </div>
</form> </form>
} }

View file

@ -5,7 +5,6 @@ import _ from 'lodash'
const PROP_LIST = [ const PROP_LIST = [
'matchChildRoutes', 'matchChildRoutes',
'hardReload',
'show', 'show',
'text', 'text',
'href', 'href',
@ -15,7 +14,6 @@ const PROP_LIST = [
class NavBarLink extends Component { class NavBarLink extends Component {
static propTypes = { static propTypes = {
matchChildRoutes: PropTypes.bool, matchChildRoutes: PropTypes.bool,
hardReload: PropTypes.bool,
show: PropTypes.bool, show: PropTypes.bool,
text: PropTypes.string, text: PropTypes.string,
href: PropTypes.string, href: PropTypes.string,
@ -29,7 +27,6 @@ class NavBarLink extends Component {
render = () => { render = () => {
const { const {
matchChildRoutes, matchChildRoutes,
hardReload,
show, show,
text, text,
href, href,
@ -45,14 +42,6 @@ class NavBarLink extends Component {
if (!show) { if (!show) {
return null return null
} }
if (hardReload) {
return (
<a { ...otherProps } href={href} className={classes.join(' ')}>
{linkClass && <div className="navBarIcon"></div>}
<div className="navBarLinkText">{text}</div>
</a>
)
}
return ( return (
<Link { ...otherProps } to={href} className={classes.join(' ')}> <Link { ...otherProps } to={href} className={classes.join(' ')}>
{linkClass && <div className="navBarIcon"></div>} {linkClass && <div className="navBarIcon"></div>}

View file

@ -16,8 +16,7 @@ class UpperLeftUI extends Component {
const { map, currentUser, userRequested, requestAnswered, requestApproved, onRequestClick } = this.props const { map, currentUser, userRequested, requestAnswered, requestApproved, onRequestClick } = this.props
return <div className="upperLeftUI"> return <div className="upperLeftUI">
<div className="homeButton"> <div className="homeButton">
{currentUser && <Link to="/">METAMAPS</Link>} <Link to="/">METAMAPS</Link>
{!currentUser && <a href="/">METAMAPS</a>}
</div> </div>
<div className="sidebarSearch"> <div className="sidebarSearch">
<input type="text" className="sidebarSearchField" placeholder="Search for topics, maps, and mappers..." /> <input type="text" className="sidebarSearchField" placeholder="Search for topics, maps, and mappers..." />

View file

@ -6,10 +6,10 @@ class Admin extends Component {
render = () => { render = () => {
return ( return (
<NavBar> <NavBar>
<NavBarLink show hardReload href="/metacode_sets" text="Metacode Sets" /> <NavBarLink show href="/metacode_sets" text="Metacode Sets" />
<NavBarLink show hardReload href="/metacode_sets/new" text="New Set" /> <NavBarLink show href="/metacode_sets/new" text="New Set" />
<NavBarLink show hardReload href="/metacodes" text="Metacodes" /> <NavBarLink show href="/metacodes" text="Metacodes" />
<NavBarLink show hardReload href="/metacodes/new" text="New Metacode" /> <NavBarLink show href="/metacodes/new" text="New Metacode" />
</NavBar> </NavBar>
) )
} }

View file

@ -9,10 +9,10 @@ class Apps extends Component {
return ( return (
<NavBar> <NavBar>
{currentUser && currentUser.get('admin') && <NavBarLink show hardReload {currentUser && currentUser.get('admin') && <NavBarLink show
matchChildRoutes href="/oauth/applications" linkClass="activeMaps" matchChildRoutes href="/oauth/applications" linkClass="activeMaps"
text="Registered Apps" />} text="Registered Apps" />}
<NavBarLink show hardReload matchChildRoutes <NavBarLink show matchChildRoutes
href="/oauth/authorized_applications" href="/oauth/authorized_applications"
linkClass="authedApps" text="Authorized Apps" /> linkClass="authedApps" text="Authorized Apps" />
<NavBarLink show href="/" linkClass="myMaps" text="Maps" /> <NavBarLink show href="/" linkClass="myMaps" text="Maps" />

View file

@ -1,4 +1,5 @@
import React, { Component } from 'react' import React, { Component } from 'react'
import { Link } from 'react-router'
class LoggedOutHome extends Component { class LoggedOutHome extends Component {
render = () => { render = () => {
@ -17,8 +18,8 @@ class LoggedOutHome extends Component {
<h3>Who finds it useful?</h3> <h3>Who finds it useful?</h3>
<p>Designers, inventors, artists, educators, strategists, consultants, facilitators, entrepreneurs, systems thinkers, changemakers, analysts, students, researchers... maybe you!</p> <p>Designers, inventors, artists, educators, strategists, consultants, facilitators, entrepreneurs, systems thinkers, changemakers, analysts, students, researchers... maybe you!</p>
<button type="button" className="button learnMoreCTA" onClick={() => {Metamaps.GlobalUI.openLightbox('about')}}>LEARN MORE</button> <button type="button" className="button learnMoreCTA" onClick={() => {Metamaps.GlobalUI.openLightbox('about')}}>LEARN MORE</button>
<a href="/explore/featured" data-router="true" className="exploreFeaturedCTA">EXPLORE FEATURED MAPS</a> <Link to="/explore/featured" data-router="true" className="exploreFeaturedCTA">EXPLORE FEATURED MAPS</Link>
<a href="/request" className="requestInviteCTA">REQUEST INVITE</a> <Link to="/request" className="requestInviteCTA">REQUEST INVITE</Link>
</div> </div>
<div className="clearfloat"></div> <div className="clearfloat"></div>
</div> </div>

246
src/routes/UserSettings.js Normal file
View file

@ -0,0 +1,246 @@
import React, { Component } from 'react'
import Loading from '../components/Loading'
class UserSettings extends Component {
constructor(props) {
super(props)
this.state = {
userImageMenuOpen: false,
changePasswordOpen: false,
changeName: false,
loading: false,
imagePreview: null,
currentPassword: '',
newPassword: '',
newPasswordConfirmation: '',
name: '',
email: '',
emailsAllowed: false,
followTopicOnCreated: false,
followTopicOnContributed: false,
followMapOnCreated: false,
followMapOnContributed: false,
removeImage: '0' // can be '0' or '1', 0 means keep, 1 means remove
}
}
componentDidMount = () => {
this.setState({
name: this.props.currentUser.get('name'),
email: this.props.currentUser.get('email'),
image: this.props.currentUser.get('image'),
emailsAllowed: this.props.currentUser.get('emails_allowed'),
followTopicOnCreated: this.props.currentUser.get('follow_topic_on_created'),
followTopicOnContributed: this.props.currentUser.get('follow_topic_on_contributed'),
followMapOnCreated: this.props.currentUser.get('follow_map_on_created'),
followMapOnContributed: this.props.currentUser.get('follow_map_on_contributed')
})
}
init = (serverData) => {
Account.userIconUrl = serverData['user.png']
}
initListeners = () => {
/*
$('#user_image').change(self.showImagePreview)
*/
}
toggleChangePicture = () => {
this.setState({
userImageMenuOpen: !this.state.userImageMenuOpen
})
}
openChangePicture = () => {
this.setState({
userImageMenuOpen: true
})
}
closeChangePicture = () => {
this.setState({
userImageMenuOpen: false
})
}
showLoading = () => {
this.setState({ loading: true })
}
showImagePreview = () => {
var file = $('#user_image')[0].files[0]
var reader = new window.FileReader()
reader.onload = function(e) {
var $canvas = $('<canvas>').attr({
width: 84,
height: 84
})
var context = $canvas[0].getContext('2d')
var imageObj = new window.Image()
imageObj.onload = function() {
$('.userImageDiv canvas').remove()
$('.userImageDiv img').hide()
var imgWidth = imageObj.width
var imgHeight = imageObj.height
var dimensionToMatch = imgWidth > imgHeight ? imgHeight : imgWidth
// draw cropped image
var nonZero = Math.abs(imgHeight - imgWidth) / 2
var sourceX = dimensionToMatch === imgWidth ? 0 : nonZero
var sourceY = dimensionToMatch === imgHeight ? 0 : nonZero
var sourceWidth = dimensionToMatch
var sourceHeight = dimensionToMatch
var destX = 0
var destY = 0
var destWidth = 84
var destHeight = 84
context.drawImage(imageObj, sourceX, sourceY, sourceWidth, sourceHeight, destX, destY, destWidth, destHeight)
$('.userImageDiv').prepend($canvas)
}
imageObj.src = reader.result
}
if (file) {
reader.readAsDataURL(file)
$('.userImageMenu').hide()
$('#remove_image').val('0')
}
}
removePicture = () => {
/*
$('.userImageDiv canvas').remove()
$('.userImageDiv img').attr('src', '/images/user.png').show()
$('.userImageMenu').hide()
var input = $('#user_image')
input.replaceWith(input.val('').clone(true))
$('#remove_image').val('1')
*/
this.setState({ removeImage: '1' })
}
changeName = () => {
this.setState({ changeName: true })
}
showPass = () => {
this.setState({ changePasswordOpen: true })
}
hidePass = () => {
this.setState({
changePasswordOpen: false,
currentPassword: '',
newPassword: '',
newPasswordConfirmation: ''
})
}
updateForKey = (key) => {
return (event) => {
const newState = {}
if (event.target.type === 'checkbox') {
newState[key] = !this.state[key]
} else {
newState[key] = event.target.value
}
this.setState(newState)
}
}
render = () => {
const id = this.props.currentUser.get('id')
const name = this.props.currentUser.get('name')
return (
<div id="yield">
<form className="edit_user centerGreyForm" id={`edit_user_${id}`} encType="multipart/form-data" action={`/users/${id}`} acceptCharset="UTF-8" method="patch">
<input name="utf8" type="hidden" value="✓" />
<h3>Edit Settings</h3>
<div className="userImage">
<div className="userImageDiv" onClick={this.toggleChangePicture}>
<img src={this.state.image} alt="11835c3" width="84" height="84" />
<div className="editPhoto"></div>
</div>
{this.state.userImageMenuOpen && <div className="userImageMenu">
<div className="userMenuArrow"></div>
<ul>
<li className="upload">
Upload Photo
<input type="hidden" name="remove_image" id="remove_image" value={this.state.removeImage} />
<input type="file" name="user[image]" id="user_image" />
<label htmlFor="user_image">Image</label>
</li>
<li className="remove" onClick={this.removePicture}>Remove</li>
<li className="cancel" onClick={this.closeChangePicture}>Cancel</li>
</ul>
</div>}
</div>
{!this.state.changeName && <div className="accountName" onClick={this.changeName}>
<div className="nameEdit">{ name }</div>
</div>}
{this.state.changeName && <div className="changeName">
<label className="firstFieldText" htmlFor="user_name">Name:</label>
<input type="text" name="user[name]" id="user_name" value={this.state.name} onChange={this.updateForKey('name')} />
</div>}
<div>
<label className="firstFieldText" htmlFor="user_email">Email:</label>
<input type="email" value={this.state.email} onChange={this.updateForKey('email')} name="user[email]" id="user_email" />
</div>
<div>
<label className="firstFieldText" htmlFor="user_emails_allowed">
<input className="inline" type="checkbox" value={this.state.emailsAllowed ? '1' : '0'} checked={this.state.emailsAllowed} onChange={this.updateForKey('emailsAllowed')} name="user[emails_allowed]" id="user_emails_allowed" />
Send Metamaps notifications to my email.
</label>
<label className="firstFieldText" htmlFor="settings_follow_topic_on_created">
<input className="inline" type="checkbox" value={this.state.followTopicOnCreated ? '1' : '0'} checked={this.state.followTopicOnCreated} onChange={this.updateForKey('followTopicOnCreated')} name="settings[follow_topic_on_created]" id="settings_follow_topic_on_created" />
Auto-follow topics you create.
</label>
<label className="firstFieldText" htmlFor="settings_follow_topic_on_contributed">
<input className="inline" type="checkbox" value={this.state.followTopicOnContributed ? '1' : '0'} checked={this.state.followTopicOnContributed} onChange={this.updateForKey('followTopicOnContributed')} name="settings[follow_topic_on_contributed]" id="settings_follow_topic_on_contributed" />
Auto-follow topics you edit.
</label>
<label className="firstFieldText" htmlFor="settings_follow_map_on_created">
<input className="inline" type="checkbox" value={this.state.followMapOnCreated ? '1' : '0'} checked={this.state.followMapOnCreated} onChange={this.updateForKey('followMapOnCreated')} name="settings[follow_map_on_created]" id="settings_follow_map_on_created" />
Auto-follow maps you create.
</label>
<label className="firstFieldText" htmlFor="settings_follow_map_on_contributed">
<input className="inline" type="checkbox" value={this.state.followMapOnContributed ? '1' : '0'} checked={this.state.followMapOnContributed} onChange={this.updateForKey('followMapOnContributed')} name="settings[follow_map_on_contributed]" id="settings_follow_map_on_contributed" />
Auto-follow maps you edit.
</label>
</div>
{!this.state.changePasswordOpen && <div className="changePass" onClick={this.showPass}>Change Password</div>}
{this.state.changePasswordOpen && <div>
<div>
<label className="firstFieldText" htmlFor="user_current_password">Current Password:</label>
<input type="password" name="current_password" id="current_password" value={this.state.currentPassword} onChange={this.updateForKey('currentPassword')} />
</div>
<div>
<label className="firstFieldText" htmlFor="user_password">New Password:</label>
<input autoComplete="off" type="password" name="user[password]" id="user_password" value={this.state.newPassword} onChange={this.updateForKey('newPassword')} />
</div>
<div>
<label className="firstFieldText" htmlFor="user_password_confirmation">Confirm New Password:</label>
<input autoComplete="off" type="password" name="user[password_confirmation]" id="user_password_confirmation" value={this.state.newPasswordConfirmation} onChange={this.updateForKey('newPasswordConfirmation')} />
</div>
<div className="noChangePass" onClick={this.hidePass}>Oops, don't change password</div>
</div>}
<div id="accountPageLoading">
{this.state.loading && <Loading />}
</div>
<input type="submit" name="commit" value="Update" className="update" onClick={this.showLoading} data-disable-with="Update" />
<div className="clearfloat"></div>
</form>
</div>
)
}
}
export default UserSettings

View file

@ -12,6 +12,7 @@ import RequestAccess from './RequestAccess'
import RequestInvite from './RequestInvite' import RequestInvite from './RequestInvite'
import Login from './Login' import Login from './Login'
import Join from './Join' import Join from './Join'
import UserSettings from './UserSettings'
function nullComponent(props) { function nullComponent(props) {
return null return null
@ -42,7 +43,7 @@ export default function makeRoutes (currentUser) {
<Route path=":id" component={Notifications} /> <Route path=":id" component={Notifications} />
</Route> </Route>
<Route path="users"> <Route path="users">
<Route path=":id/edit" component={nullComponent} /> <Route path=":id/edit" component={UserSettings} />
<Route path="password" component={nullComponent} /> <Route path="password" component={nullComponent} />
<Route path="password/new" component={nullComponent} /> <Route path="password/new" component={nullComponent} />
<Route path="password/edit" component={nullComponent} /> <Route path="password/edit" component={nullComponent} />

View file

@ -1,89 +0,0 @@
import React, { Component } from react
class MyComponent extends Component {
render = () => {
return (
<div id="yield">
{ form_for @user, url: user_url, :html =>{ :multipart => true, :className => "edit_user centerGreyForm"} do |form| }
<h3>Edit Settings</h3>
<div className="userImage">
<div className="userImageDiv" onclick="Metamaps.Account.toggleChangePicture()">
{ image_tag @user.image.url(:ninetysix), :size => "84x84" }
<div className="editPhoto"></div>
</div>
<div className="userImageMenu">
<div className="userMenuArrow"></div>
<ul>
<li className="upload">
Upload Photo
{ hidden_field_tag "remove_image", "0" }
{ form.file_field :image }
{ form.label :image }
</li>
<li className="remove" onclick="Metamaps.Account.removePicture()">Remove</li>
<li className="cancel" onclick="Metamaps.Account.closeChangePicture()">Cancel</li>
</ul>
</div>
</div>
<div className="accountName" onclick="Metamaps.Account.changeName()">
<div className="nameEdit">{ @user.name }</div>
</div>
<div className="changeName">
{ form.label :name, "Name:", className: 'firstFieldText' }
{ form.text_field :name }
</div>
<div>
{ form.label :email, "Email:", className: 'firstFieldText' }
{ form.email_field :email }
</div>
<div>
{ form.label :emails_allowed, className: 'firstFieldText' do }
{ form.check_box :emails_allowed, className: 'inline' }
Send Metamaps notifications to my email.
{ end }
{ fields_for :settings, @user.settings do |settings| }
{ settings.label :follow_topic_on_created, className: 'firstFieldText' do }
{ settings.check_box :follow_topic_on_created, className: 'inline' }
Auto-follow topics you create.
{ end }
{ settings.label :follow_topic_on_contributed, className: 'firstFieldText' do }
{ settings.check_box :follow_topic_on_contributed, className: 'inline' }
Auto-follow topics you edit.
{ end }
{ settings.label :follow_map_on_created, className: 'firstFieldText' do }
{ settings.check_box :follow_map_on_created, className: 'inline' }
Auto-follow maps you create.
{ end }
{ settings.label :follow_map_on_contributed, className: 'firstFieldText' do }
{ settings.check_box :follow_map_on_contributed, className: 'inline' }
Auto-follow maps you edit.
{ end }
{ end }
</div>
<div className="changePass" onclick="Metamaps.Account.showPass()">Change Password</div>
<div className="toHide">
<div>
{ form.label :current_password, "Current Password:", :className => "firstFieldText" }
{ password_field_tag :current_password, params[:current_password] }
</div>
<div>
{ form.label :password, "New Password:", :className => "firstFieldText" }
{ form.password_field :password, :autocomplete => :off}
</div>
<div>
{ form.label :password_confirmation, "Confirm New Password:", :className => "firstFieldText" }
{ form.password_field :password_confirmation, :autocomplete => :off}
</div>
<div className="noChangePass" onclick="Metamaps.Account.hidePass()">Oops, don't change password</div>
</div>
<div id="accountPageLoading"></div>
{ form.submit "Update", className: "update", onclick: "Metamaps.Account.showLoading()" }
<div className="clearfloat"></div>
{ end }
</div>
)
}
}
export default MyComponent