Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
M
Moecube Accounts Web
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Locked Files
Issues
0
Issues
0
List
Boards
Labels
Service Desk
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Security & Compliance
Security & Compliance
Dependency List
License Compliance
Packages
Packages
List
Container Registry
Analytics
Analytics
CI / CD
Code Review
Insights
Issues
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
MyCard
Moecube Accounts Web
Commits
1a7177ed
Commit
1a7177ed
authored
Apr 06, 2017
by
2breakegg
Browse files
Options
Browse Files
Download
Plain Diff
Merge remote-tracking branch 'origin/master'
parents
8e8fb944
0373d4e2
Changes
32
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
32 changed files
with
714 additions
and
467 deletions
+714
-467
.editorconfig
.editorconfig
+9
-5
.eslintrc
.eslintrc
+6
-1
.idea/workspace.xml
.idea/workspace.xml
+434
-265
.roadhogrc.js
.roadhogrc.js
+1
-1
.travis.yml
.travis.yml
+18
-0
i18n.json
i18n.json
+0
-1
package.json
package.json
+3
-3
public/index.html
public/index.html
+2
-1
src/components/EmailForm.js
src/components/EmailForm.js
+3
-1
src/components/PasswordForm.js
src/components/PasswordForm.js
+19
-15
src/components/SendEmail.js
src/components/SendEmail.js
+5
-2
src/components/UserNameForm.js
src/components/UserNameForm.js
+3
-1
src/config.js
src/config.js
+2
-2
src/index.js
src/index.js
+1
-1
src/index.less
src/index.less
+2
-2
src/models/auth.js
src/models/auth.js
+2
-1
src/models/user.js
src/models/user.js
+10
-2
src/router.js
src/router.js
+1
-1
src/routes/Activate.js
src/routes/Activate.js
+1
-1
src/routes/Forgot.js
src/routes/Forgot.js
+9
-4
src/routes/Index.js
src/routes/Index.js
+9
-5
src/routes/Index.less
src/routes/Index.less
+9
-9
src/routes/Login.js
src/routes/Login.js
+9
-3
src/routes/Profiles.js
src/routes/Profiles.js
+53
-54
src/routes/Register.js
src/routes/Register.js
+26
-17
src/routes/Register.less
src/routes/Register.less
+1
-2
src/routes/Reset.js
src/routes/Reset.js
+12
-10
src/routes/Verify.js
src/routes/Verify.js
+17
-9
src/services/auth.js
src/services/auth.js
+7
-7
src/services/upload.js
src/services/upload.js
+2
-3
src/utils/request.js
src/utils/request.js
+15
-13
src/utils/sso.js
src/utils/sso.js
+23
-25
No files found.
.editorconfig
View file @
1a7177ed
...
...
@@ -2,15 +2,19 @@
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
trim_trailing_whitespace = true
end_of_line = lf
[*.md]
max_line_length = 0
trim_trailing_whitespace = false
[Makefile]
indent_style = tab
# Indentation override
#[lib/**.js]
#[{package.json,.travis.yml}]
#[**/**.js]
.eslintrc
View file @
1a7177ed
...
...
@@ -23,11 +23,16 @@
"no-bitwise": [0],
"no-cond-assign": [0],
"import/no-unresolved": [0],
"require-yield": [1]
"require-yield": [1],
"react/react-in-jsx-scope": [0],
"no-extra-semi": [0]
},
"parserOptions": {
"ecmaFeatures": {
"experimentalObjectRestSpread": true
}
},
"env": {
"browser": true
}
}
.idea/workspace.xml
View file @
1a7177ed
This diff is collapsed.
Click to expand it.
.roadhogrc.js
View file @
1a7177ed
let
publicPath
=
'
https://cdn01.moecube.com/accounts/
'
let
defineConf
=
{
apiRoot
:
process
.
env
[
"
BUILD
"
]
==
'
development
'
?
'
http://114.215.243.95:8082
'
:
'
https://api.moeube.com/accounts
'
apiRoot
:
process
.
env
[
"
API_ROOT
"
]
}
export
default
{
...
...
.travis.yml
0 → 100644
View file @
1a7177ed
language
:
node_js
node_js
:
node
env
:
global
:
secure
:
NObcZ6fY1VQuoDfxRxKVOZ+p7g3LTDkonG4Ow4HIbx2g8wJ24mMqs9gN0J3Asbdbz68isDMpkKy7IW1mK9+N9fM0pBauqD1YMbglnEv+HhYjhiEsQdRdDM2nzDIjS4PCwavI1Da5TLhaUjSAM4lrHx7bVOK4YsvF3s8JEApS54QgSlbeJgvSbPcCiapl0VwwaL36cGndChc3tawq4xseuk4bP2NrTEd7ifYZMt+iojId+UuhRQk4w0HUlBhEDKiT/fLxeQDwMRv2WIdIPW7D7+Wo01iX+T0Ti629QhQBe/S76affkG6G085HIPin3VvXDQaiYbK4ALbc79O+9jqSxEFd9nwG8xbp2jezzvclUSXPhIyZe7VSRS6z1MdevlyQa56AUEP7My7IMqj8j7NPoUgrnVlKtR8WPHQacfAVkrcOIX+Tzwl2IMOCqonamDtJjUNX5xpYB+IEj+INvQmRqT2NicExGWj9LZp3L3kscwq1u+0hPzgoQ9yovE+OvLFNE/R5AE90GIaSlwXw4MqOeB+8l+ou2JzNZFJhHBvAsOFwQTloFz/pu7ichJ+P0KsMPteLFA4Btuo6bBu31K7R310CmlIdYJIeeybMuM6e6bG8IkbVcMq5skg9LNa64KuDG46oopwGLiWkdRwDzG3VmXGwVm+OF2EWZi/B0wIcTwY=
NODE_ENV
:
development
script
:
npm run build
before_deploy
:
-
curl --location --retry 5 --output ossutil 'https://github.com/mycard/ossutil/releases/download/1.0.0.Beta2/ossutil'
-
chmod +x ossutil
-
./ossutil config --endpoint oss-cn-hangzhou.aliyuncs.com --access-key-id $ALIYUN_ID
--access-key-secret $ALIYUN_SECRET
deploy
:
provider
:
script
script
:
./ossutil cp -rf dist oss://mycard/accounts
skip_cleanup
:
true
on
:
branch
:
master
i18n.json
View file @
1a7177ed
...
...
@@ -159,7 +159,6 @@
"i_not_found"
:
"用户不存在"
,
"i_key_time_out"
:
"此链接已过期"
,
"i_key_invalid"
:
"此链接已失效"
,
"没毛用"
:
"防逗号报错,上线删"
}
...
...
package.json
View file @
1a7177ed
{
"private"
:
true
,
"scripts"
:
{
"start"
:
"cross-env
BUILD=development
roadhog server"
,
"build:dev"
:
"cross-env
BUILD=development
roadhog build"
,
"build"
:
"roadhog build"
,
"start"
:
"cross-env
API_ROOT=http://192.168.1.9:3000
roadhog server"
,
"build:dev"
:
"cross-env
API_ROOT=http://114.215.243.95:8082
roadhog build"
,
"build"
:
"
cross-env API_ROOT=https://api.moeube.com/accounts
roadhog build"
,
"lint"
:
"eslint --ext .js src test"
,
"precommit"
:
"npm run lint"
},
...
...
public/index.html
View file @
1a7177ed
...
...
@@ -3,7 +3,8 @@
<head>
<meta
charset=
"UTF-8"
>
<meta
name=
"viewport"
content=
"width=device-width, initial-scale=1"
>
<link
rel=
"stylesheet"
href=
"index.css"
/>
<link
rel=
"stylesheet"
href=
"index.css"
/>
<link
rel=
"icon"
href=
"https://moecube.com/favicon.ico"
>
</head>
<body>
...
...
src/components/EmailForm.js
View file @
1a7177ed
...
...
@@ -18,7 +18,9 @@ class EmailForm extends React.Component {
onSubmit
=
(
e
)
=>
{
const
{
form
,
dispatch
,
user
:
{
id
}
}
=
this
.
props
;
e
&&
e
.
preventDefault
();
if
(
e
)
{
e
.
preventDefault
();
}
form
.
validateFieldsAndScroll
((
err
,
values
)
=>
{
if
(
!
err
)
{
console
.
log
(
'
Received values of form:
'
,
values
);
...
...
src/components/PasswordForm.js
View file @
1a7177ed
...
...
@@ -18,6 +18,24 @@ class EmailForm extends React.Component {
static
contextTypes
=
{
intl
:
PropTypes
.
object
.
isRequired
,
};
onSubmit
=
(
e
)
=>
{
const
{
form
,
dispatch
,
user
:
{
id
}
}
=
this
.
props
;
if
(
e
)
{
e
.
preventDefault
();
}
form
.
validateFieldsAndScroll
((
err
,
values
)
=>
{
if
(
!
err
)
{
console
.
log
(
'
Received values of form:
'
,
values
);
const
{
new_password
,
password
}
=
values
;
dispatch
({
type
:
'
user/updateAccount
'
,
payload
:
{
new_password
,
password
,
user_id
:
id
}
});
}
});
};
checkPassword
=
(
rule
,
value
,
callback
)
=>
{
const
form
=
this
.
props
.
form
;
const
{
intl
:
{
messages
}
}
=
this
.
context
;
...
...
@@ -36,20 +54,6 @@ class EmailForm extends React.Component {
callback
();
};
onSubmit
=
(
e
)
=>
{
const
{
form
,
dispatch
,
user
:
{
id
}
}
=
this
.
props
;
e
&&
e
.
preventDefault
();
form
.
validateFieldsAndScroll
((
err
,
values
)
=>
{
if
(
!
err
)
{
console
.
log
(
'
Received values of form:
'
,
values
);
const
{
new_password
,
password
}
=
values
;
dispatch
({
type
:
'
user/updateAccount
'
,
payload
:
{
new_password
,
password
,
user_id
:
id
}
});
}
});
};
render
()
{
const
{
form
}
=
this
.
props
;
...
...
@@ -131,7 +135,7 @@ class EmailForm extends React.Component {
}
function
mapStateToProps
(
state
,
props
)
{
function
mapStateToProps
(
state
)
{
const
{
user
:
{
user
},
}
=
state
;
...
...
src/components/SendEmail.js
View file @
1a7177ed
...
...
@@ -18,7 +18,9 @@ class EmailForm extends React.Component {
onSubmit
=
(
e
)
=>
{
const
{
form
,
dispatch
,
user
:
{
id
}
}
=
this
.
props
;
e
&&
e
.
preventDefault
();
if
(
e
)
{
e
.
preventDefault
();
}
form
.
validateFieldsAndScroll
((
err
,
values
)
=>
{
if
(
!
err
)
{
console
.
log
(
'
Received values of form:
'
,
values
);
...
...
@@ -59,7 +61,8 @@ class EmailForm extends React.Component {
{
getFieldDecorator
(
'
email
'
,
{
...
emailProps
.
decorator
})(
<
Input
{...
emailProps
.
input
}
onBlur
=
{()
=>
dispatch
({
type
:
'
auth/checkEmail
'
,
payload
:
{
...
form
.
getFieldsValue
(),
id
}
})}
/>
,
onBlur
=
{()
=>
dispatch
({
type
:
'
auth/checkEmail
'
,
payload
:
{
...
form
.
getFieldsValue
(),
id
}
})}
/>
,
)}
<
/FormItem
>
...
...
src/components/UserNameForm.js
View file @
1a7177ed
...
...
@@ -18,7 +18,9 @@ class EmailForm extends React.Component {
onSubmit
=
(
e
)
=>
{
const
{
form
,
dispatch
,
user
:
{
id
}
}
=
this
.
props
;
e
&&
e
.
preventDefault
();
if
(
e
)
{
e
.
preventDefault
();
}
form
.
validateFieldsAndScroll
((
err
,
values
)
=>
{
if
(
!
err
)
{
console
.
log
(
'
Received values of form:
'
,
values
);
...
...
src/config.js
View file @
1a7177ed
/* global apiRoot */
export
default
{
apiRoot
:
apiRoot
apiRoot
,
};
src/index.js
View file @
1a7177ed
...
...
@@ -11,7 +11,7 @@ import './index.less';
// 1. Initialize
const
app
=
dva
({
onError
:
(
error
,
dispatch
)
=>
{
onError
:
(
error
)
=>
{
message
.
destroy
();
message
.
error
(
error
.
message
);
},
...
...
src/index.less
View file @
1a7177ed
html, body, :global(#root) {
height: 100%;
min-height: 100%;
}
:global {
...
...
@@ -21,4 +21,4 @@ html, body, :global(#root) {
opacity: 0.01;
transition: opacity 300ms ease-in;
}
}
\ No newline at end of file
}
src/models/auth.js
View file @
1a7177ed
...
...
@@ -22,8 +22,9 @@ export default {
},
reducers
:
{
signOut
(
state
)
{
console
.
log
(
'
sign out
'
);
localStorage
.
removeItem
(
'
token
'
);
location
=
'
/
'
;
location
.
href
=
'
/
'
;
return
state
;
},
change
(
state
,
action
)
{
...
...
src/models/user.js
View file @
1a7177ed
...
...
@@ -144,7 +144,7 @@ export default {
if
(
data
.
active
)
{
yield
put
(
routerRedux
.
replace
(
'
/profiles
'
));
}
else
{
yield
put
(
routerRedux
.
replace
(
'
/
verify
'
));
yield
put
(
routerRedux
.
replace
(
'
/
signin
'
));
}
}
}
catch
(
error
)
{
...
...
@@ -204,11 +204,19 @@ export default {
const
token
=
localStorage
.
getItem
(
'
token
'
);
if
(
token
)
{
dispatch
({
type
:
'
getAuthUser
'
,
payload
:
{
token
}
});
}
else
if
(
location
.
pathname
===
'
/profiles
'
)
{
dispatch
(
routerRedux
.
replace
(
'
/signin
'
));
}
history
.
listen
(({
pathname
})
=>
{
history
.
listen
(({
pathname
,
query
})
=>
{
if
(
pathname
===
'
/
'
)
{
dispatch
({
type
:
'
preLogin
'
,
payload
:
{
token
}
});
}
if
(
pathname
===
'
/reset
'
||
pathname
===
'
/activate
'
)
{
if
(
!
query
.
key
)
{
message
.
error
(
'
缺少参数
'
);
}
}
});
},
},
...
...
src/router.js
View file @
1a7177ed
import
{
R
edirect
,
R
oute
,
Router
}
from
'
dva/router
'
;
import
{
Route
,
Router
}
from
'
dva/router
'
;
import
React
from
'
react
'
;
import
Active
from
'
./routes/Activate.js
'
;
...
...
src/routes/Activate.js
View file @
1a7177ed
...
...
@@ -14,7 +14,7 @@ class Active extends React.Component {
render
()
{
const
{
loading
}
=
this
.
props
;
return
(
<
div
style
=
{{
display
:
'
flex
'
,
justifyContent
:
'
center
'
,
alignItems
:
'
center
'
,
flex
:
1
}}
>
<
div
style
=
{{
display
:
'
flex
'
,
flex
:
1
,
justifyContent
:
'
center
'
,
alignItems
:
'
center
'
,
height
:
'
100%
'
}}
>
<
Button
type
=
"
primary
"
icon
=
"
poweroff
"
loading
=
{
loading
}
onClick
=
{
this
.
handleClick
}
>
<
Format
id
=
{
'
verify-email
'
}
/
>
<
/Button
>
...
...
src/routes/Forgot.js
View file @
1a7177ed
...
...
@@ -14,9 +14,11 @@ class Login extends React.Component {
};
onSubmitLogin
=
(
e
)
=>
{
const
{
form
,
dispatch
,
params
:
{
id
}
}
=
this
.
props
;
const
{
form
,
dispatch
}
=
this
.
props
;
e
&&
e
.
preventDefault
();
if
(
e
)
{
e
.
preventDefault
();
}
form
.
validateFieldsAndScroll
((
err
,
values
)
=>
{
if
(
!
err
)
{
console
.
log
(
'
Received values of form:
'
,
values
);
...
...
@@ -33,7 +35,7 @@ class Login extends React.Component {
const
{
loading
}
=
this
.
props
;
const
{
intl
:
{
messages
}
}
=
this
.
context
;
return
(
<
div
style
=
{{
display
:
'
flex
'
,
justifyContent
:
'
center
'
,
alignItems
:
'
center
'
,
height
:
'
100%
'
}}
>
<
div
style
=
{{
display
:
'
flex
'
,
flex
:
1
,
justifyContent
:
'
center
'
,
alignItems
:
'
center
'
,
height
:
'
100%
'
}}
>
<
Spin
spinning
=
{
loading
}
delay
=
{
100
}
>
<
Form
onSubmit
=
{
this
.
onSubmitLogin
}
className
=
"
login-form
"
>
...
...
@@ -46,7 +48,10 @@ class Login extends React.Component {
{
getFieldDecorator
(
'
email
'
,
{
rules
:
[{
required
:
true
,
message
:
'
Please input your username or email!
'
}],
})(
<
Input
prefix
=
{
<
Icon
type
=
"
user
"
style
=
{{
fontSize
:
13
}}
/>} placeholder={messages
[
'email-address-or-username'
]
}/
>
,
<
Input
prefix
=
{
<
Icon
type
=
"
user
"
style
=
{{
fontSize
:
13
}}
/>
}
placeholder
=
{
messages
[
'
email-address-or-username
'
]}
/>
,
)}
<
/FormItem
>
...
...
src/routes/Index.js
View file @
1a7177ed
...
...
@@ -139,12 +139,12 @@ function Index({ children, messages, dispatch }) {
<
/Menu
>
);
return
(
<
div
style
=
{{
height
:
'
100%
'
}}
>
<
DocumentTitle
title
=
{
messages
.
title
||
'
Moe Cube
'
}
/
>
<
div
style
=
{{
display
:
'
flex
'
,
flexDirection
:
'
column
'
,
flex
:
1
,
minHeight
:
'
100%
'
}}
>
<
DocumentTitle
title
=
{
messages
.
title
||
'
Moe Cube
'
}
/
>
<
Header
style
=
{{
display
:
'
flex
'
,
alignItems
:
'
center
'
}}
>
<
Link
to
=
"
/
"
style
=
{{
marginTop
:
'
20px
'
}}
>
<
img
src
=
{
logo
}
style
=
{{
width
:
'
140px
'
,
height
:
'
44px
'
}}
/
>
<
img
alt
=
"
logo
"
src
=
{
logo
}
style
=
{{
width
:
'
140px
'
,
height
:
'
44px
'
}}
/
>
<
/Link
>
<
Menu
...
...
@@ -167,7 +167,11 @@ function Index({ children, messages, dispatch }) {
style
=
{{
lineHeight
:
'
64px
'
,
position
:
'
absolute
'
,
right
:
'
50px
'
}}
>
{
localStorage
.
getItem
(
'
token
'
)
?
(
<
Menu
.
Item
key
=
"
1
"
>
<
div
onClick
=
{()
=>
{
dispatch
({
type
:
'
auth/signOut
'
})
}}
>
<
div
onClick
=
{()
=>
{
dispatch
({
type
:
'
auth/signOut
'
});
}}
>
<
Format
id
=
"
sign-out
"
/>
<
/div
>
<
/Menu.Item>
)
:
(
''
)
...
...
@@ -196,7 +200,7 @@ function Index({ children, messages, dispatch }) {
/
>
{
children
}
<
Footer
style
=
{{
position
:
'
absolute
'
,
width
:
'
100%
'
}}
>
<
Footer
style
=
{{
width
:
'
100%
'
}}
>
<
div
>
©
MoeCube
2017
all
right
reserved
.
<
/div
>
<
/Footer
>
<
/div
>
...
...
src/routes/Index.less
View file @
1a7177ed
.particles {
position: fixed;
top: 0;
left: 0;
z-index: 1;
width: 100%;
height: 100%;
}
\ No newline at end of file
.particles {
position: fixed;
top: 0;
left: 0;
z-index: 1;
width: 100%;
height: 100%;
}
src/routes/Login.js
View file @
1a7177ed
...
...
@@ -35,7 +35,7 @@ class Login extends React.Component {
const
{
intl
:
{
messages
}
}
=
this
.
context
;
return
(
<
div
style
=
{{
display
:
'
flex
'
,
justifyContent
:
'
center
'
,
alignItems
:
'
center
'
,
height
:
'
100%
'
}}
>
<
div
style
=
{{
display
:
'
flex
'
,
flex
:
1
,
justifyContent
:
'
center
'
,
alignItems
:
'
center
'
,
height
:
'
100%
'
}}
>
<
Spin
spinning
=
{
loading
}
delay
=
{
100
}
>
<
Form
onSubmit
=
{
this
.
onSubmitLogin
}
className
=
"
login-form
"
>
...
...
@@ -43,14 +43,20 @@ class Login extends React.Component {
{
getFieldDecorator
(
'
account
'
,
{
rules
:
[{
required
:
true
,
message
:
messages
[
'
Please input your account!
'
]
}],
})(
<
Input
prefix
=
{
<
Icon
type
=
"
user
"
style
=
{{
fontSize
:
13
}}
/>} placeholder={messages
[
'email-address-or-username'
]
}/
>
,
<
Input
prefix
=
{
<
Icon
type
=
"
user
"
style
=
{{
fontSize
:
13
}}
/>
}
placeholder
=
{
messages
[
'
email-address-or-username
'
]}
/>
,
)}
<
/FormItem
>
<
FormItem
>
{
getFieldDecorator
(
'
password
'
,
{
rules
:
[{
required
:
true
,
message
:
messages
[
'
Please-input-your-Password!
'
]
}],
})(
<
Input
prefix
=
{
<
Icon
type
=
"
lock
"
style
=
{{
fontSize
:
13
}}
/>} type="password" placeholder={messages.password}/
>
,
<
Input
prefix
=
{
<
Icon
type
=
"
lock
"
style
=
{{
fontSize
:
13
}}
/>} type="password
"
placeholder
=
{
messages
.
password
}
/>
,
)}
<
/FormItem
>
<
FormItem
>
...
...
src/routes/Profiles.js
View file @
1a7177ed
...
...
@@ -19,11 +19,11 @@ const formItemLayout = {
wrapperCol
:
{
span
:
15
},
};
function
getBase64
(
img
,
callback
)
{
const
reader
=
new
FileReader
();
reader
.
addEventListener
(
'
load
'
,
()
=>
callback
(
reader
.
result
));
reader
.
readAsDataURL
(
img
);
}
//
function getBase64(img, callback) {
//
const reader = new FileReader();
//
reader.addEventListener('load', () => callback(reader.result));
//
reader.readAsDataURL(img);
//
}
class
Profiles
extends
React
.
Component
{
...
...
@@ -31,15 +31,20 @@ class Profiles extends React.Component {
intl
:
PropTypes
.
object
.
isRequired
,
};
handleUpload
=
()
=>
{
if
(
typeof
this
.
cropper
.
getCroppedCanvas
()
===
'
undefined
'
)
{
return
;
onUpdateSubmit
=
(
e
)
=>
{
const
{
form
,
dispatch
,
user
:
{
id
}
}
=
this
.
props
;
if
(
e
)
{
e
.
preventDefault
();
}
const
{
user
:
{
id
}
}
=
this
.
props
;
form
.
validateFieldsAndScroll
((
err
,
values
)
=>
{
if
(
!
err
)
{
console
.
log
(
'
Received values of form:
'
,
values
);
this
.
cropper
.
getCroppedCanvas
().
toBlob
(
blob
=>
{
console
.
log
(
blob
);
this
.
props
.
dispatch
({
type
:
'
upload/upload
'
,
payload
:
{
image
:
blob
,
user_id
:
id
}
});
const
{
username
,
name
,
password
}
=
values
;
dispatch
({
type
:
'
user/updateProfile
'
,
payload
:
{
username
,
name
,
password
,
user_id
:
id
}
});
}
});
};
...
...
@@ -57,18 +62,15 @@ class Profiles extends React.Component {
reader
.
readAsDataURL
(
files
[
0
]);
};
onUpdateSubmit
=
(
e
)
=>
{
const
{
form
,
dispatch
,
user
:
{
id
},
}
=
this
.
props
;
e
&&
e
.
preventDefault
();
form
.
validateFieldsAndScroll
((
err
,
values
)
=>
{
if
(
!
err
)
{
console
.
log
(
'
Received values of form:
'
,
values
);
const
{
username
,
name
,
password
}
=
values
;
handleUpload
=
()
=>
{
if
(
typeof
this
.
cropper
.
getCroppedCanvas
()
===
'
undefined
'
)
{
return
;
}
const
{
user
:
{
id
}
}
=
this
.
props
;
dispatch
({
type
:
'
user/updateProfile
'
,
payload
:
{
username
,
name
,
password
,
user_id
:
id
}
});
}
this
.
cropper
.
getCroppedCanvas
().
toBlob
((
blob
)
=>
{
console
.
log
(
blob
);
this
.
props
.
dispatch
({
type
:
'
upload/upload
'
,
payload
:
{
image
:
blob
,
user_id
:
id
}
});
});
};
...
...
@@ -91,7 +93,7 @@ class Profiles extends React.Component {
},
};
/* eslint-disable jsx-a11y/label-has-for */
return
(
<
Spin
spinning
=
{
loading
}
delay
=
{
100
}
>
<
Tabs
defaultActiveKey
=
"
1
"
className
=
"
app-detail-nav
"
>
...
...
@@ -99,37 +101,34 @@ class Profiles extends React.Component {
<
Form
onSubmit
=
{
this
.
onUpdateSubmit
}
>
<
FormItem
style
=
{{
display
:
'
flex
'
,
justifyContent
:
'
center
'
}}
>
{
isUpload
?
<
div
>
<
Cropper
ref
=
{
cropper
=>
{
this
.
cropper
=
cropper
;
}}
src
=
{
imageUrl
||
defaultAvatar
}
style
=
{{
height
:
'
20vw
'
,
width
:
'
20vw
'
}}
aspectRatio
=
{
1
/
1
}
guides
=
{
true
}
/
>
<
Button
>
<
label
>
<
Icon
type
=
"
plus
"
/>
add
file
<
input
type
=
"
file
"
onChange
=
{
this
.
onGetFile
}
ref
=
{
file
=>
{
this
.
file
=
file
;
}}
style
=
{{
display
:
'
none
'
}}
/
>
<
/label
>
<
/Button
>
<
Button
type
=
"
primary
"
onClick
=
{
this
.
handleUpload
}
>
<
Icon
type
=
"
upload
"
/>
upload
<
/Button
>
<
/div
>
:
<
img
src
=
{
avatar
||
imageUrl
||
defaultAvatar
}
style
=
{{
height
:
'
256px
'
,
width
:
'
256px
'
}}
onClick
=
{()
=>
dispatch
({
type
:
'
upload/start
'
})}
/
>
}
<
div
style
=
{{
display
:
isUpload
?
'
flex
'
:
'
none
'
,
flexDirection
:
'
column
'
}}
>
<
Cropper
ref
=
{(
cropper
)
=>
{
this
.
cropper
=
cropper
;
}}
src
=
{
imageUrl
||
defaultAvatar
}
style
=
{{
height
:
'
20vw
'
,
width
:
'
20vw
'
}}
aspectRatio
=
{
1
/
1
}
guides
/>
<
Button
type
=
"
primary
"
onClick
=
{
this
.
handleUpload
}
>
<
Icon
type
=
"
upload
"
/>
upload
<
/Button
>
<
/div
>
<
div
style
=
{{
display
:
!
isUpload
?
'
flex
'
:
'
none
'
,
flexDirection
:
'
column
'
}}
>
<
img
alt
=
"
avatar
"
src
=
{
avatar
||
imageUrl
||
defaultAvatar
}
/
>
<
Button
onClick
=
{()
=>
{
dispatch
({
type
:
'
upload/start
'
});
}}
>
<
label
>
<
Icon
type
=
"
plus
"
/>
Change
Avatar
<
input
type
=
"
file
"
onChange
=
{
this
.
onGetFile
}
ref
=
{(
file
)
=>
{
this
.
file
=
file
;
}}
style
=
{{
display
:
'
none
'
}}
/
>
<
/label
>
<
/Button
>
<
/div
>
<
/FormItem
>
<
FormItem
{...
nameProps
.
fromItem
}
>
...
...
src/routes/Register.js
View file @
1a7177ed
import
{
Button
,
Form
,
Icon
,
Input
,
S
elect
,
S
pin
,
Steps
}
from
'
antd
'
;
import
{
Button
,
Form
,
Icon
,
Input
,
Spin
,
Steps
}
from
'
antd
'
;
import
{
connect
}
from
'
dva
'
;
import
{
Link
}
from
'
dva/router
'
;
import
React
,
{
PropTypes
}
from
'
react
'
;
import
{
FormattedMessage
as
Format
}
from
'
react-intl
'
;
const
FormItem
=
Form
.
Item
;
const
Option
=
Select
.
Option
;
const
Step
=
Steps
.
Step
;
...
...
@@ -16,14 +15,16 @@ class Register extends React.Component {
};
onSubmitLogin
=
(
e
)
=>
{
const
{
form
,
dispatch
,
params
:
{
id
}
}
=
this
.
props
;
const
{
form
,
dispatch
}
=
this
.
props
;
e
&&
e
.
preventDefault
();
if
(
e
)
{
e
.
preventDefault
();
}
form
.
validateFieldsAndScroll
((
err
,
values
)
=>
{
if
(
!
err
)
{
console
.
log
(
'
Received values of form:
'
,
values
);
const
{
email
,
username
,
password
,
}
=
values
;
const
{
email
,
username
,
password
}
=
values
;
dispatch
({
type
:
'
auth/register
'
,
payload
:
{
email
,
username
,
password
}
});
}
...
...
@@ -49,35 +50,38 @@ class Register extends React.Component {
};
render
()
{
const
{
dispatch
,
register
,
form
,
checkEmail
,
checkUsername
,
isEmailExists
,
isUserNameExists
,
isRegisterSubmit
,
loading
}
=
this
.
props
;
const
{
getFieldDecorator
,
}
=
form
;
const
{
email
,
username
,
password
}
=
register
;
const
{
dispatch
,
form
,
checkEmail
,
checkUsername
,
isEmailExists
,
isUserNameExists
,
loading
,
}
=
this
.
props
;
const
{
getFieldDecorator
}
=
form
;
const
{
intl
:
{
messages
}
}
=
this
.
context
;
const
emailProps
=
{
hasFeedback
:
true
,
validateStatus
:
checkEmail
,
help
:
isEmailExists
?
messages
[
'
i_email_exists
'
]
:
''
,
extra
:
isEmailExists
?
messages
.
i_email_exists
:
''
,
};
const
emailInputProps
=
{
onBlur
:
()
=>
dispatch
({
type
:
'
auth/checkEmail
'
,
payload
:
{
...
form
.
getFieldsValue
()
}
}),
onBlur
:
()
=>
!
form
.
getFieldError
(
'
email
'
)
&&
dispatch
({
type
:
'
auth/checkEmail
'
,
payload
:
{
...
form
.
getFieldsValue
()
}
}),
placeholder
:
messages
.
email
,
};
const
usernameProps
=
{
hasFeedback
:
true
,
validateStatus
:
checkUsername
,
help
:
isUserNameExists
?
'
username exists
'
:
''
,
extra
:
isUserNameExists
?
'
username exists
'
:
''
,
};
const
usernameInputProps
=
{
onBlur
:
()
=>
dispatch
({
type
:
'
auth/checkUsername
'
,
payload
:
{
...
form
.
getFieldsValue
()
}
}),
onBlur
:
()
=>
!
form
.
getFieldError
(
'
username
'
)
&&
dispatch
({
type
:
'
auth/checkUsername
'
,
payload
:
{
...
form
.
getFieldsValue
()
}
}),
placeholder
:
messages
.
username
,
};
return
(
<
div
style
=
{{
display
:
'
flex
'
,
justifyContent
:
'
center
'
,
alignItems
:
'
center
'
,
height
:
'
100%
'
}}
>
<
div
style
=
{{
display
:
'
flex
'
,
justifyContent
:
'
center
'
,
alignItems
:
'
center
'
,
height
:
'
100%
'
}}
>
<
Spin
spinning
=
{
loading
}
delay
=
{
100
}
>
<
Steps
size
=
"
large
"
current
=
{
0
}
>
...
...
@@ -86,7 +90,7 @@ class Register extends React.Component {
<
/Steps
>
<
Form
onSubmit
=
{
this
.
onSubmitLogin
}
className
=
"
login-form
"
style
=
{{
marginTop
:
'
24px
'
}}
>
<
FormItem
{...
emailProps
}
>
<
FormItem
{...
emailProps
}
>
{
getFieldDecorator
(
'
email
'
,
{
rules
:
[{
required
:
true
,
...
...
@@ -123,7 +127,8 @@ class Register extends React.Component {
<
Input
prefix
=
{
<
Icon
type
=
"
lock
"
style
=
{{
fontSize
:
13
}}
/>
}
type
=
"
password
"
placeholder
=
{
messages
.
password
}
/>
,
placeholder
=
{
messages
.
password
}
/>
,
)}
<
/FormItem
>
...
...
@@ -141,7 +146,8 @@ class Register extends React.Component {
prefix
=
{
<
Icon
type
=
"
lock
"
style
=
{{
fontSize
:
13
}}
/>
}
type
=
"
password
"
onBlur
=
{
this
.
handleConfirmBlur
}
placeholder
=
{
messages
[
'
password-again
'
]}
/>
,
placeholder
=
{
messages
[
'
password-again
'
]}
/>
,
)}
<
/FormItem
>
...
...
@@ -163,7 +169,10 @@ class Register extends React.Component {
function
mapStateToProps
(
state
)
{
const
{
auth
:
{
register
,
checkEmail
,
checkUsername
,
isEmailExists
,
isUserNameExists
,
isRegisterSubmit
},
auth
:
{
register
,
checkEmail
,
checkUsername
,
isEmailExists
,
isUserNameExists
,
isRegisterSubmit
,
},
}
=
state
;
const
loading
=
state
.
loading
.
global
||
false
;
...
...
src/routes/Register.less
View file @
1a7177ed
...
...
@@ -20,7 +20,6 @@
}
}
.action {
margin-top: 24px;
}
\ No newline at end of file
}
src/routes/Reset.js
View file @
1a7177ed
import
{
Button
,
Form
,
Icon
,
Input
,
S
elect
,
S
pin
}
from
'
antd
'
;
import
{
Button
,
Form
,
Icon
,
Input
,
Spin
}
from
'
antd
'
;
import
{
connect
}
from
'
dva
'
;
import
React
,
{
PropTypes
}
from
'
react
'
;
import
{
FormattedMessage
as
Format
}
from
'
react-intl
'
;
const
FormItem
=
Form
.
Item
;
const
Option
=
Select
.
Option
;
class
Reset
extends
React
.
Component
{
...
...
@@ -16,7 +14,9 @@ class Reset extends React.Component {
onSubmitReset
=
(
e
)
=>
{
const
{
form
,
dispatch
,
location
:
{
query
:
{
key
,
user_id
}
}
}
=
this
.
props
;
e
&&
e
.
preventDefault
();
if
(
e
)
{
e
.
preventDefault
();
}
form
.
validateFieldsAndScroll
((
err
,
values
)
=>
{
if
(
!
err
)
{
console
.
log
(
'
Received values of form:
'
,
values
);
...
...
@@ -51,11 +51,11 @@ class Reset extends React.Component {
const
{
intl
:
{
messages
}
}
=
this
.
context
;
return
(
<
div
style
=
{{
display
:
'
flex
'
,
justifyContent
:
'
center
'
,
alignItems
:
'
center
'
,
height
:
'
100%
'
}}
>
<
div
style
=
{{
display
:
'
flex
'
,
flex
:
1
,
justifyContent
:
'
center
'
,
alignItems
:
'
center
'
,
height
:
'
100%
'
}}
>
<
Spin
spinning
=
{
isResetSubmit
}
delay
=
{
100
}
>
<
Form
onSubmit
=
{
this
.
onSubmitReset
}
className
=
"
login-form
"
>
<
FormItem
>
<
h1
><
Format
id
=
'
reset-password2
'
/><
/h1
>
<
h1
><
Format
id
=
"
reset-password2
"
/><
/h1
>
<
/FormItem
>
<
FormItem
>
{
getFieldDecorator
(
'
password
'
,
{
...
...
@@ -63,8 +63,10 @@ class Reset extends React.Component {
},
{
validator
:
this
.
checkConfirm
,
})(
<
Input
prefix
=
{
<
Icon
type
=
"
lock
"
style
=
{{
fontSize
:
13
}}
/>} type="password
"
placeholder
=
{
messages
.
password
}
/>
,
<
Input
prefix
=
{
<
Icon
type
=
"
lock
"
style
=
{{
fontSize
:
13
}}
/>} type="password
"
placeholder
=
{
messages
.
password
}
/>
,
)}
<
/FormItem
>
...
...
@@ -81,7 +83,7 @@ class Reset extends React.Component {
<
/FormItem
>
<
Button
type
=
"
primary
"
htmlType
=
"
submit
"
className
=
"
login-form-button
"
>
<
Format
id
=
'
reset-password2
'
/>
<
Format
id
=
"
reset-password2
"
/>
<
/Button
>
<
/Form
>
<
/Spin
>
...
...
@@ -90,7 +92,7 @@ class Reset extends React.Component {
}
}
function
mapStateToProps
(
state
,
props
)
{
function
mapStateToProps
(
state
)
{
const
{
auth
:
{
isResetSubmit
},
}
=
state
;
...
...
src/routes/Verify.js
View file @
1a7177ed
...
...
@@ -4,6 +4,7 @@ import { routerRedux } from 'dva/router';
import
React
,
{
PropTypes
}
from
'
react
'
;
import
{
FormattedMessage
as
Format
}
from
'
react-intl
'
;
import
SubmitButton
from
'
../components/SubmitButton
'
;
const
FormItem
=
Form
.
Item
;
const
Step
=
Steps
.
Step
;
...
...
@@ -20,7 +21,9 @@ class Verify extends React.Component {
onSubmit
=
(
e
)
=>
{
const
{
form
,
dispatch
,
input
:
{
password
},
user
:
{
id
}
}
=
this
.
props
;
e
&&
e
.
preventDefault
();
if
(
e
)
{
e
.
preventDefault
();
}
form
.
validateFieldsAndScroll
((
err
,
values
)
=>
{
if
(
!
err
)
{
console
.
log
(
'
Received values of form:
'
,
values
);
...
...
@@ -35,12 +38,14 @@ class Verify extends React.Component {
onReSend
=
(
e
)
=>
{
const
{
dispatch
,
input
:
{
password
},
user
:
{
id
,
email
}
}
=
this
.
props
;
e
&&
e
.
preventDefault
();
if
(
e
)
{
e
.
preventDefault
();
}
dispatch
({
type
:
'
user/updateEmail
'
,
payload
:
{
email
,
password
,
user_id
:
id
}
});
};
render
(
select
)
{
render
()
{
const
{
form
,
dispatch
,
user
,
checkEmail
,
isEmailExists
,
loading
,
input
}
=
this
.
props
;
const
{
getFieldDecorator
}
=
form
;
const
{
id
,
email
}
=
user
;
...
...
@@ -64,7 +69,8 @@ class Verify extends React.Component {
return
(
<
div
style
=
{{
display
:
'
flex
'
,
flexDirection
:
'
column
'
,
alignItems
:
'
center
'
,
justifyContent
:
'
center
'
,
flex
:
1
}}
>
style
=
{{
display
:
'
flex
'
,
flex
:
1
,
justifyContent
:
'
center
'
,
alignItems
:
'
center
'
,
height
:
'
100%
'
}}
>
<
Spin
spinning
=
{
loading
}
delay
=
{
100
}
>
<
Steps
size
=
"
large
"
current
=
{
1
}
>
...
...
@@ -72,7 +78,7 @@ class Verify extends React.Component {
<
Step
title
=
{
messages
[
'
verify-email
'
]}
icon
=
{
<
Icon
type
=
"
mail
"
/>
}
/
>
<
/Steps
>
{
id
&&
input
[
'
password
'
]
?
{
id
&&
input
.
password
?
<
Alert
style
=
{{
marginTop
:
'
24px
'
}}
message
=
{
...
...
@@ -93,8 +99,9 @@ class Verify extends React.Component {
message
=
{
<
div
>
<
span
style
=
{{
marginRight
:
'
10px
'
}}
><
Format
id
=
{
'
Please-sign-in
'
}
/></
span
>
<
Tag
color
=
"
blue
"
onClick
=
{
()
=>
dispatch
(
routerRedux
.
replace
(
'
/signin
'
))}
><
Format
id
=
{
'
sign-in
'
}
/></
Tag
>
<
Tag
color
=
"
blue
"
onClick
=
{()
=>
dispatch
(
routerRedux
.
replace
(
'
/signin
'
))}
><
Format
id
=
{
'
sign-in
'
}
/></
Tag
>
<
/div
>
}
type
=
"
warning
"
...
...
@@ -109,7 +116,8 @@ class Verify extends React.Component {
{
getFieldDecorator
(
'
email
'
,
{
...
emailProps
.
decorator
})(
<
Input
{...
emailProps
.
input
}
onBlur
=
{()
=>
dispatch
({
type
:
'
auth/checkEmail
'
,
payload
:
{
...
form
.
getFieldsValue
(),
id
}
})}
/>
,
onBlur
=
{()
=>
dispatch
({
type
:
'
auth/checkEmail
'
,
payload
:
{
...
form
.
getFieldsValue
(),
id
}
})}
/>
,
)}
<
/FormItem
>
...
...
@@ -125,7 +133,7 @@ class Verify extends React.Component {
}
}
function
mapStateToProps
(
state
,
props
)
{
function
mapStateToProps
(
state
)
{
const
{
user
:
{
user
},
auth
:
{
input
,
isEmailExists
,
checkEmail
},
...
...
src/services/auth.js
View file @
1a7177ed
import
request
from
'
../utils/request
'
;
export
async
function
login
(
params
)
{
return
request
(
`/signin`
,
{
return
request
(
'
/signin
'
,
{
method
:
'
POST
'
,
body
:
JSON
.
stringify
(
params
),
});
}
export
async
function
forgot
(
params
)
{
return
request
(
`/forgot`
,
{
return
request
(
'
/forgot
'
,
{
method
:
'
POST
'
,
body
:
JSON
.
stringify
(
params
),
});
}
export
async
function
register
(
params
)
{
return
request
(
`/signup`
,
{
return
request
(
'
/signup
'
,
{
method
:
'
POST
'
,
body
:
JSON
.
stringify
(
params
),
});
}
export
async
function
reset
(
params
)
{
return
request
(
`/reset`
,
{
return
request
(
'
/reset
'
,
{
method
:
'
PATCH
'
,
body
:
JSON
.
stringify
(
params
),
});
}
export
async
function
activate
(
params
)
{
return
request
(
`/activate`
,
{
return
request
(
'
/activate
'
,
{
method
:
'
POST
'
,
body
:
JSON
.
stringify
(
params
),
});
...
...
@@ -50,7 +50,7 @@ export async function getUserByUsername(params) {
}
export
async
function
checkUserExists
(
params
)
{
return
request
(
`/user/exists`
,
{
return
request
(
'
/user/exists
'
,
{
method
:
'
POST
'
,
body
:
JSON
.
stringify
(
params
),
});
...
...
@@ -58,7 +58,7 @@ export async function checkUserExists(params) {
export
async
function
getAuthUser
(
params
)
{
return
request
(
`/authUser`
,
{
return
request
(
'
/authUser
'
,
{
method
:
'
GET
'
,
headers
:
{
Authorization
:
`Bearer
${
params
.
token
}
`
,
...
...
src/services/upload.js
View file @
1a7177ed
import
request
from
'
../utils/request
'
;
export
async
function
uploadImage
(
params
)
{
console
.
log
(
params
);
le
t
data
=
new
FormData
();
data
.
append
(
'
file
'
,
params
[
'
image
'
]
);
cons
t
data
=
new
FormData
();
data
.
append
(
'
file
'
,
params
.
image
);
return
request
(
'
/upload/image
'
,
{
method
:
'
POST
'
,
...
...
src/utils/request.js
View file @
1a7177ed
...
...
@@ -10,11 +10,11 @@ async function checkStatus(response) {
return
response
;
}
let
message
let
message
;
try
{
message
=
(
await
response
.
json
())
[
"
message
"
]
message
=
(
await
response
.
json
())
.
message
;
}
catch
(
error
)
{
message
=
response
.
statusText
message
=
response
.
statusText
;
}
const
error
=
new
Error
(
message
);
...
...
@@ -29,17 +29,19 @@ async function checkStatus(response) {
* @param {object} [options] The options we want to pass to "fetch"
* @return {object} An object containing either "data" or "err"
*/
export
default
function
request
(
url
,
options
)
{
url
=
`
${
config
.
apiRoot
}${
url
}
`
if
(
options
&&
!
options
.
headers
)
{
options
.
headers
=
{
"
content-type
"
:
"
application/json
"
}
export
default
function
request
(
relativeUrl
,
options
)
{
const
url
=
`
${
config
.
apiRoot
}${
relativeUrl
}
`
;
if
(
options
&&
!
options
.
headers
)
{
Object
.
assign
(
options
,
{
headers
:
{
'
content-type
'
:
'
application/json
'
,
},
});
}
console
.
log
(
options
)
console
.
log
(
options
)
;
return
fetch
(
url
,
options
)
.
then
(
checkStatus
)
.
then
(
parseJSON
)
.
then
(
data
=>
({
data
}))
// .catch(err => ({ err }));
}
.
then
(
data
=>
({
data
}))
;
// .catch(err => ({ err }));
}
;
src/utils/sso.js
View file @
1a7177ed
import
crypto
from
'
crypto
'
import
"
url-api-polyfill
"
;
import
crypto
from
'
crypto
'
;
import
'
url-api-polyfill
'
;
const
url
=
new
URL
(
window
.
location
)
let
sso
let
ssoString
=
url
.
searchParams
.
get
(
'
sso
'
)
let
sso
;
const
ssoString
=
new
URL
(
window
.
location
).
searchParams
.
get
(
'
sso
'
);
if
(
ssoString
)
{
sso
=
new
URLSearchParams
(
Buffer
.
from
(
ssoString
,
'
base64
'
).
toString
())
sso
=
new
URLSearchParams
(
Buffer
.
from
(
ssoString
,
'
base64
'
).
toString
())
;
}
export
const
handleSSO
=
(
user
)
=>
{
if
(
sso
)
{
let
params
=
new
URLSearchParams
()
let
url
=
new
URL
(
sso
.
get
(
"
return_sso_url
"
));
if
(
sso
)
{
const
params
=
new
URLSearchParams
();
const
url
=
new
URL
(
sso
.
get
(
'
return_sso_url
'
));
for
(
le
t
[
key
,
value
]
of
Object
.
entries
(
user
))
{
params
.
set
(
key
,
value
)
for
(
cons
t
[
key
,
value
]
of
Object
.
entries
(
user
))
{
params
.
set
(
key
,
value
);
}
params
.
set
(
"
return_sso_url
"
,
sso
.
get
(
"
return_sso_url
"
))
params
.
set
(
"
nonce
"
,
sso
.
get
(
"
nonce
"
))
params
.
set
(
"
external_id
"
,
user
.
id
)
let
payload
=
Buffer
.
from
(
params
.
toString
()).
toString
(
'
base64
'
)
url
.
searchParams
.
set
(
"
sso
"
,
payload
)
url
.
searchParams
.
set
(
'
sig
'
,
crypto
.
createHmac
(
'
sha256
'
,
'
zsZv6LXHDwwtUAGa
'
).
update
(
payload
).
digest
(
'
hex
'
))
window
.
location
.
href
=
url
return
true
}
else
{
return
false
params
.
set
(
'
return_sso_url
'
,
sso
.
get
(
'
return_sso_url
'
));
params
.
set
(
'
nonce
'
,
sso
.
get
(
'
nonce
'
));
params
.
set
(
'
external_id
'
,
user
.
id
);
const
payload
=
Buffer
.
from
(
params
.
toString
()).
toString
(
'
base64
'
);
url
.
searchParams
.
set
(
'
sso
'
,
payload
);
url
.
searchParams
.
set
(
'
sig
'
,
crypto
.
createHmac
(
'
sha256
'
,
'
zsZv6LXHDwwtUAGa
'
).
update
(
payload
).
digest
(
'
hex
'
))
;
window
.
location
.
href
=
url
;
return
true
;
}
else
{
return
false
;
}
}
\ No newline at end of file
};
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment