mirror of
https://github.com/TryGhost/Ghost-Admin.git
synced 2023-12-14 02:33:04 +01:00
First pass at user onboarding screens
refs #5315 - split setup into 3 screens - add gravatar fetching - add download counter - add button handling for invite users
This commit is contained in:
parent
66e9a95fe7
commit
e0e32b8bf1
22 changed files with 338 additions and 48 deletions
|
@ -61,6 +61,7 @@ app.import('bower_components/codemirror/mode/css/css.js');
|
|||
app.import('bower_components/codemirror/mode/javascript/javascript.js');
|
||||
app.import('bower_components/xregexp/xregexp-all.js');
|
||||
app.import('bower_components/password-generator/lib/password-generator.js');
|
||||
app.import('bower_components/blueimp-md5/js/md5.js');
|
||||
|
||||
// 'dem Styles
|
||||
app.import('bower_components/nprogress/nprogress.css');
|
||||
|
|
|
@ -3,6 +3,9 @@ var ActivatingListItem = Ember.Component.extend({
|
|||
tagName: 'li',
|
||||
classNameBindings: ['active'],
|
||||
active: false,
|
||||
linkClasses: Ember.computed('linkClass', function () {
|
||||
return this.get('linkClass');
|
||||
}),
|
||||
|
||||
unfocusLink: function () {
|
||||
this.$('a').blur();
|
||||
|
|
27
app/controllers/setup/one.js
Normal file
27
app/controllers/setup/one.js
Normal file
|
@ -0,0 +1,27 @@
|
|||
import Ember from 'ember';
|
||||
import ajax from 'ghost/utils/ajax';
|
||||
|
||||
var SetupOneController = Ember.Controller.extend({
|
||||
|
||||
count: 'many, many',
|
||||
|
||||
downloadCounter: function () {
|
||||
var self = this,
|
||||
interval = 3000;
|
||||
|
||||
Ember.run.later(this, function () {
|
||||
ajax({
|
||||
url: self.get('ghostPaths.count'),
|
||||
type: 'GET'
|
||||
}).then(function (data) {
|
||||
self.set('count', data.count.toLocaleString());
|
||||
}).catch(function () {
|
||||
self.set('count', 'many, many');
|
||||
});
|
||||
|
||||
this.downloadCounter();
|
||||
}, interval);
|
||||
}.on('init')
|
||||
});
|
||||
|
||||
export default SetupOneController;
|
43
app/controllers/setup/three.js
Normal file
43
app/controllers/setup/three.js
Normal file
|
@ -0,0 +1,43 @@
|
|||
import Ember from 'ember';
|
||||
import ValidationEngine from 'ghost/mixins/validation-engine';
|
||||
|
||||
var SetupThreeController = Ember.Controller.extend(ValidationEngine, {
|
||||
users: '',
|
||||
usersArray: Ember.computed('users', function () {
|
||||
return this.get('users').split('\n').filter(function (user) {
|
||||
return validator.isEmail(user);
|
||||
});
|
||||
}),
|
||||
numUsers: Ember.computed('usersArray', function () {
|
||||
return this.get('usersArray').length;
|
||||
}),
|
||||
buttonText: Ember.computed('numUsers', function () {
|
||||
var user = this.get('numUsers') === 1 ? 'user' : 'users';
|
||||
return this.get('numUsers') > 0 ?
|
||||
'Invite ' + this.get('numUsers') + ' ' + user : 'I\'ll do this later, take me to my blog!';
|
||||
}),
|
||||
buttonClass: Ember.computed('numUsers', function () {
|
||||
return this.get('numUsers') > 0 ? 'btn-green' : 'btn-minor';
|
||||
}),
|
||||
actions: {
|
||||
invite: function () {
|
||||
console.log('inviting', this.get('usersArray'));
|
||||
|
||||
if (this.get('numUsers') === 0) {
|
||||
this.sendAction('signin');
|
||||
}
|
||||
|
||||
// TODO: do invites
|
||||
},
|
||||
signin: function () {
|
||||
var self = this;
|
||||
|
||||
this.get('session').authenticate('simple-auth-authenticator:oauth2-password-grant', {
|
||||
identification: self.get('email'),
|
||||
password: self.get('password')
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default SetupThreeController;
|
70
app/controllers/setup/two.js
Normal file
70
app/controllers/setup/two.js
Normal file
|
@ -0,0 +1,70 @@
|
|||
/* global md5 */
|
||||
import Ember from 'ember';
|
||||
import ajax from 'ghost/utils/ajax';
|
||||
import ValidationEngine from 'ghost/mixins/validation-engine';
|
||||
|
||||
var SetupTwoController = Ember.Controller.extend(ValidationEngine, {
|
||||
size: 90,
|
||||
blogTitle: null,
|
||||
name: null,
|
||||
email: '',
|
||||
password: null,
|
||||
image: null,
|
||||
submitting: false,
|
||||
|
||||
gravatarUrl: Ember.computed('email', function () {
|
||||
var email = this.get('email'),
|
||||
size = this.get('size');
|
||||
|
||||
return 'http://www.gravatar.com/avatar/' + md5(email) + '?s=' + size + '&d=blank';
|
||||
}),
|
||||
|
||||
userImage: Ember.computed('gravatarUrl', function () {
|
||||
return this.get('image') || this.get('gravatarUrl');
|
||||
}),
|
||||
|
||||
userImageBackground: Ember.computed('userImage', function () {
|
||||
return 'background-image: url(' + this.get('userImage') + ')';
|
||||
}),
|
||||
|
||||
// ValidationEngine settings
|
||||
validationType: 'setup',
|
||||
|
||||
actions: {
|
||||
setup: function () {
|
||||
var self = this,
|
||||
data = self.getProperties('blogTitle', 'name', 'email', 'password');
|
||||
|
||||
self.notifications.closePassive();
|
||||
|
||||
this.toggleProperty('submitting');
|
||||
this.validate({format: false}).then(function () {
|
||||
ajax({
|
||||
url: self.get('ghostPaths.url').api('authentication', 'setup'),
|
||||
type: 'POST',
|
||||
data: {
|
||||
setup: [{
|
||||
name: data.name,
|
||||
email: data.email,
|
||||
password: data.password,
|
||||
blogTitle: data.blogTitle
|
||||
}]
|
||||
}
|
||||
}).then(function () {
|
||||
self.get('session').authenticate('simple-auth-authenticator:oauth2-password-grant', {
|
||||
identification: self.get('email'),
|
||||
password: self.get('password')
|
||||
});
|
||||
}).catch(function (resp) {
|
||||
self.toggleProperty('submitting');
|
||||
self.notifications.showAPIError(resp);
|
||||
});
|
||||
}).catch(function (errors) {
|
||||
self.toggleProperty('submitting');
|
||||
self.notifications.showErrors(errors);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export default SetupTwoController;
|
|
@ -9,8 +9,8 @@
|
|||
<meta name="MobileOptimized" content="320" />
|
||||
<meta name="viewport" content="user-scalable=no, width=device-width, initial-scale=1, maximum-scale=1, minimal-ui" />
|
||||
<link rel="stylesheet" type="text/css" href="https://fonts.googleapis.com/css?family=Open+Sans:400,300,700" />
|
||||
<link rel="stylesheet" href="file:///Users/John/Sites/Ghost/core/built/assets/vendor.css" />
|
||||
<link rel="stylesheet" href="file:///Users/John/Sites/Ghost/core/built/assets/ghost.css" />
|
||||
<link rel="stylesheet" href="../../../built/assets/vendor.css" />
|
||||
<link rel="stylesheet" href="../../../built/assets/ghost.css" />
|
||||
</head>
|
||||
<body>
|
||||
|
||||
|
|
|
@ -15,7 +15,12 @@ var Router = Ember.Router.extend({
|
|||
documentTitle();
|
||||
|
||||
Router.map(function () {
|
||||
this.route('setup');
|
||||
this.resource('setup', function () {
|
||||
this.route('one');
|
||||
this.route('two');
|
||||
this.route('three');
|
||||
});
|
||||
|
||||
this.route('signin');
|
||||
this.route('signout');
|
||||
this.route('signup', {path: '/signup/:token'});
|
||||
|
|
9
app/routes/setup/index.js
Normal file
9
app/routes/setup/index.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
import Ember from 'ember';
|
||||
|
||||
var SetupRoute = Ember.Route.extend({
|
||||
beforeModel: function () {
|
||||
this.transitionTo('setup.one');
|
||||
}
|
||||
});
|
||||
|
||||
export default SetupRoute;
|
24
app/routes/setup/one.js
Normal file
24
app/routes/setup/one.js
Normal file
|
@ -0,0 +1,24 @@
|
|||
import Ember from 'ember';
|
||||
import ajax from 'ghost/utils/ajax';
|
||||
|
||||
var SetupOneRoute = Ember.Route.extend({
|
||||
titleToken: 'Setup',
|
||||
beforeModel: function () {
|
||||
var self = this,
|
||||
ctrl = this.controllerFor('setup.one');
|
||||
|
||||
if (!ctrl) {
|
||||
this.generateController('setup.one');
|
||||
ctrl = this.controllerFor('setup.one');
|
||||
}
|
||||
|
||||
return ajax({
|
||||
url: self.get('ghostPaths.count'),
|
||||
type: 'GET'
|
||||
}).then(function (data) {
|
||||
ctrl.set('count', data.count.toLocaleString());
|
||||
}).catch(function () { /* Do nothing */ });
|
||||
}
|
||||
});
|
||||
|
||||
export default SetupOneRoute;
|
7
app/routes/setup/three.js
Normal file
7
app/routes/setup/three.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
import Ember from 'ember';
|
||||
|
||||
var SetupTwoRoute = Ember.Route.extend({
|
||||
titleToken: 'Setup'
|
||||
});
|
||||
|
||||
export default SetupTwoRoute;
|
7
app/routes/setup/two.js
Normal file
7
app/routes/setup/two.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
import Ember from 'ember';
|
||||
|
||||
var SetupTwoRoute = Ember.Route.extend({
|
||||
titleToken: 'Setup'
|
||||
});
|
||||
|
||||
export default SetupTwoRoute;
|
|
@ -77,7 +77,7 @@
|
|||
background-repeat: repeat-x;
|
||||
}
|
||||
|
||||
.gh-flow-nav .current ~ .divider {
|
||||
.gh-flow-nav .active ~ .divider {
|
||||
background-image: linear-gradient(to right, #e3e3e3 33%, rgba(255, 255, 255, 0) 0%);
|
||||
}
|
||||
|
||||
|
@ -107,32 +107,32 @@
|
|||
line-height: 22px;
|
||||
}
|
||||
|
||||
.gh-flow-nav .current ~ li:not(divider) .step {
|
||||
.gh-flow-nav .active ~ li:not(divider) .step {
|
||||
border: #e3e3e3 2px solid;
|
||||
background: transparent;
|
||||
color: #cdcdcd;
|
||||
}
|
||||
|
||||
.gh-flow-nav .current ~ li:not(divider) .step .num {
|
||||
.gh-flow-nav .active ~ li:not(divider) .step .num {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.gh-flow-nav .current ~ li:not(divider) .step i {
|
||||
.gh-flow-nav .active ~ li:not(divider) .step i {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.gh-flow-nav .current .step {
|
||||
.gh-flow-nav .active .step {
|
||||
border: var(--green) 2px solid;
|
||||
background: transparent;
|
||||
color: color(var(--green) lightness(-10%));
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.gh-flow-nav .current .step .num {
|
||||
.gh-flow-nav .active .step .num {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.gh-flow-nav .current .step i {
|
||||
.gh-flow-nav .active .step i {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
@ -257,7 +257,7 @@
|
|||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.gh-flow-content .img {
|
||||
.gh-flow-content .placeholder-img {
|
||||
display: block;
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
|
@ -268,6 +268,24 @@
|
|||
animation: fade-in 1s;
|
||||
}
|
||||
|
||||
.gh-flow-content .gravatar-img {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
display: block;
|
||||
box-sizing: content-box;
|
||||
width: calc(100% - 8px);
|
||||
width: 90px;
|
||||
height: 90px;
|
||||
border: #fff 4px solid;
|
||||
background-position: center center;
|
||||
background-size: cover;
|
||||
border-radius: 100%;
|
||||
animation: fade-in 1s;
|
||||
}
|
||||
|
||||
.gh-flow-content .form-group {
|
||||
margin-bottom: 2.5rem;
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
{{#link-to route alternateActive=active}}{{title}}{{yield}}{{/link-to}}
|
||||
{{#link-to route alternateActive=active classNameBindings="linkClasses"}}{{title}}{{yield}}{{/link-to}}
|
||||
|
|
|
@ -1,38 +1,27 @@
|
|||
<section class="setup-box js-setup-box fade-in">
|
||||
<div class="vertical">
|
||||
<form id="setup" class="setup-form" method="post" novalidate="novalidate">
|
||||
<div class="gh-flow">
|
||||
|
||||
{{!-- Horrible hack to prevent Chrome from incorrectly auto-filling inputs --}}
|
||||
<input style="display:none;" type="text" name="fakeusernameremembered"/>
|
||||
<input style="display:none;" type="password" name="fakepasswordremembered"/>
|
||||
<header class="gh-flow-head">
|
||||
<nav class="gh-flow-nav">
|
||||
{{!-- TODO: this should only appear on screens 2 & 3 --}}
|
||||
<a class="gh-flow-back" href="#"><i class="icon-arrow-left"></i> Back</a>
|
||||
<ol>
|
||||
{{#gh-activating-list-item route="setup.one" linkClass="step"}}
|
||||
<i class="icon-check"></i><span class="num">1</span>
|
||||
{{/gh-activating-list-item}}
|
||||
<li class="divider"></li>
|
||||
{{#gh-activating-list-item route="setup.two" linkClass="step"}}
|
||||
<i class="icon-check"></i><span class="num">2</span>
|
||||
{{/gh-activating-list-item}}
|
||||
<li class="divider"></li>
|
||||
{{#gh-activating-list-item route="setup.three" linkClass="step"}}
|
||||
<i class="icon-check"></i><span class="num">3</span>
|
||||
{{/gh-activating-list-item}}
|
||||
</ol>
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
<header>
|
||||
<h1>Welcome to your new Ghost blog</h1>
|
||||
<h2>Let's get a few things set up so you can get started.</h2>
|
||||
</header>
|
||||
<div class="form-group">
|
||||
<label for="blog-title">Blog Title</label>
|
||||
{{input type="text" name="blog-title" autofocus="autofocus" autocorrect="off" value=blogTitle }}
|
||||
<p>What would you like to call your blog?</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="name">Full Name</label>
|
||||
{{input type="text" name="name" autofocus="autofocus" autocorrect="off" value=name }}
|
||||
<p>The name that you will sign your posts with</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="email">Email Address</label>
|
||||
{{input type="email" name="email" autofocus="autofocus" autocorrect="off" value=email }}
|
||||
<p>Used for important notifications</p>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">Password</label>
|
||||
{{input type="password" name="password" autofocus="autofocus" autocorrect="off" value=password }}
|
||||
<p>Must be at least 8 characters</p>
|
||||
</div>
|
||||
<footer>
|
||||
<button type="submit" class="btn btn-green btn-lg" {{action "setup"}} disabled={{submitting}}>Ok, Let's Do This</button>
|
||||
</footer>
|
||||
</form>
|
||||
<div class="gh-flow-content-wrap">
|
||||
{{outlet}}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
</div>
|
||||
|
|
12
app/templates/setup/one.hbs
Normal file
12
app/templates/setup/one.hbs
Normal file
|
@ -0,0 +1,12 @@
|
|||
<section class="gh-flow-content">
|
||||
<header>
|
||||
<h1>Welcome to <strong>Ghost</strong>!</h1>
|
||||
<p>So far there have been <em>{{count}}</em> Ghost blogs made by people all over the world. Today we’re making yours.</p>
|
||||
</header>
|
||||
|
||||
<img class="gh-flow-screenshot" src="{{gh-path 'admin' 'img/install-welcome.png'}}" alt="Ghost screenshot" />
|
||||
|
||||
{{#link-to "setup.two" classNames="btn btn-green btn-lg"}}
|
||||
Create your account <i class="icon-chevron"></i>
|
||||
{{/link-to}}
|
||||
</section>
|
18
app/templates/setup/three.hbs
Normal file
18
app/templates/setup/three.hbs
Normal file
|
@ -0,0 +1,18 @@
|
|||
<section class="gh-flow-content">
|
||||
<header>
|
||||
<h1>Invite your team</h1>
|
||||
<p>Ghost works best when shared with others. Collaborate, get feedback on your posts & work together on ideas.</p>
|
||||
</header>
|
||||
|
||||
<img class="gh-flow-faces" src="{{gh-path 'admin' 'img/users.png'}}" alt="" />
|
||||
|
||||
<form class="gh-flow-invite">
|
||||
<label>Enter one email address per line, we’ll handle the rest! <i class="icon-mail"></i></label>
|
||||
{{textarea name="users" placeholder="john@example.com
|
||||
sally.sanders@example.com" value=users}}
|
||||
</form>
|
||||
|
||||
<button {{action "signin"}} class="btn btn-default btn-lg btn-block {{buttonClass}}">
|
||||
{{buttonText}}
|
||||
</button>
|
||||
</section>
|
55
app/templates/setup/two.hbs
Normal file
55
app/templates/setup/two.hbs
Normal file
|
@ -0,0 +1,55 @@
|
|||
<section class="gh-flow-content">
|
||||
<header>
|
||||
<h1>Create your account</h1>
|
||||
</header>
|
||||
|
||||
<form id="setup" class="gh-flow-create">
|
||||
{{!-- Horrible hack to prevent Chrome from incorrectly auto-filling inputs --}}
|
||||
<input style="display:none;" type="text" name="fakeusernameremembered"/>
|
||||
<input style="display:none;" type="password" name="fakepasswordremembered"/>
|
||||
|
||||
|
||||
<figure class="account-image">
|
||||
<div class="placeholder-img" style="background-image: url({{gh-path 'admin' 'img/ghosticon.jpg'}})"></div>
|
||||
|
||||
<!-- TODO: fix/change this to prevent XSS -->
|
||||
<div id="account-image" class="gravatar-img" style="{{userImageBackground}}">
|
||||
<span class="sr-only">User image</span>
|
||||
</div>
|
||||
<a class="edit-account-image" href="#"><i class="icon-photos"><span class="sr-only">Upload an image</span></i></a>
|
||||
</figure>
|
||||
<div class="form-group">
|
||||
<label for="email-address">Email address</label>
|
||||
<span class="input-icon icon-mail">
|
||||
{{input type="email" name="email" placeholder="Eg. john@example.com" class="gh-input" autofocus="autofocus" autocorrect="off" value=email}}
|
||||
</span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="full-name">Full name</label>
|
||||
<span class="input-icon icon-user">
|
||||
{{input type="text" name="name" placeholder="Eg. John H. Watson" class="gh-input" autofocus="autofocus" autocorrect="off" value=name }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="password">Password</label>
|
||||
<span class="input-icon icon-lock">
|
||||
{{input type="password" name="password" placeholder="At least 7 characters" class="gh-input" autofocus="autofocus" autocorrect="off" value=password }}
|
||||
<div class="pw-strength">
|
||||
<div class="pw-strength-dot"></div>
|
||||
<div class="pw-strength-dot"></div>
|
||||
<div class="pw-strength-dot"></div>
|
||||
<div class="pw-strength-dot"></div>
|
||||
<div class="pw-strength-dot <!--pw-strength-activedot-->"></div>
|
||||
</div>
|
||||
</span>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="blog-title">Blog title</label>
|
||||
<span class="input-icon icon-content">
|
||||
{{input type="text" name="blog-title" placeholder="Eg. The Daily Awesome" class="gh-input" autofocus="autofocus" autocorrect="off" value=blogTitle }}
|
||||
</span>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<button type="submit" class="btn btn-green btn-lg btn-block" {{action "setup"}} {{submitting}}>Last step: Invite your team <i class="icon-chevron"></i></button>
|
||||
</section>
|
|
@ -53,7 +53,8 @@ function ghostPaths() {
|
|||
},
|
||||
|
||||
asset: assetUrl
|
||||
}
|
||||
},
|
||||
count: 'https://ghost.org/count/'
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
{
|
||||
"name": "ghost",
|
||||
"dependencies": {
|
||||
"blueimp-md5": "1.1.0",
|
||||
"codemirror": "5.2.0",
|
||||
"devicejs": "0.2.7",
|
||||
"ember": "1.11.3",
|
||||
|
|
BIN
public/assets/img/ghosticon.jpg
Normal file
BIN
public/assets/img/ghosticon.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.4 KiB |
BIN
public/assets/img/install-welcome.png
Normal file
BIN
public/assets/img/install-welcome.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 235 KiB |
BIN
public/assets/img/users.png
Normal file
BIN
public/assets/img/users.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 48 KiB |
Loading…
Reference in a new issue