1
0
Fork 0
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:
Hannah Wolfe 2015-03-29 20:10:53 +02:00
parent 66e9a95fe7
commit e0e32b8bf1
22 changed files with 338 additions and 48 deletions

View file

@ -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');

View file

@ -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();

View 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;

View 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;

View 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;

View file

@ -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>

View file

@ -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'});

View 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
View 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;

View 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
View file

@ -0,0 +1,7 @@
import Ember from 'ember';
var SetupTwoRoute = Ember.Route.extend({
titleToken: 'Setup'
});
export default SetupTwoRoute;

View file

@ -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;
}

View file

@ -1 +1 @@
{{#link-to route alternateActive=active}}{{title}}{{yield}}{{/link-to}}
{{#link-to route alternateActive=active classNameBindings="linkClasses"}}{{title}}{{yield}}{{/link-to}}

View file

@ -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>

View 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 were 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>

View 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 &amp; 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, well 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>

View 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>

View file

@ -53,7 +53,8 @@ function ghostPaths() {
},
asset: assetUrl
}
},
count: 'https://ghost.org/count/'
};
}

View file

@ -1,6 +1,7 @@
{
"name": "ghost",
"dependencies": {
"blueimp-md5": "1.1.0",
"codemirror": "5.2.0",
"devicejs": "0.2.7",
"ember": "1.11.3",

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 235 KiB

BIN
public/assets/img/users.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB