🎨 User profile settings page layout updates (#695)

closes TryGhost/Ghost#7134

Overhaul of the user settings page to make it more consistent with other settings panels.
The hardly readable validation for user "Full Name" is redundant as well, as the input field for it now has the same styles as the other input fields.
This commit is contained in:
Aileen Nowak 2017-05-17 21:24:31 +09:00 committed by Kevin Ansfield
parent fed5dc8a79
commit 67ded0c8ed
5 changed files with 183 additions and 281 deletions

View File

@ -16,9 +16,9 @@ export function countCharacters(params) {
el.className = 'word-count'; el.className = 'word-count';
if (length > 180) { if (length > 180) {
el.style.color = '#E25440'; el.style.color = '#f05230';
} else { } else {
el.style.color = '#9E9D95'; el.style.color = '#738a94';
} }
el.innerHTML = 200 - length; el.innerHTML = 200 - length;

View File

@ -39,37 +39,16 @@
padding: 0; padding: 0;
} }
@media (min-width: 901px) {
.content.settings-user {
padding: 0 40px;
}
}
.user-cover { .user-cover {
position: relative; display: block;
overflow: hidden; overflow: hidden;
margin: 0; margin: 0;
width: auto; width: 100%;
height: 300px; height: 300px;
margin-bottom: 30px;
background: #fafafa no-repeat center center; background: #fafafa no-repeat center center;
background-size: cover; background-size: cover;
} border-radius: 6px;
@media (max-width: 900px) {
.user-cover {
margin: 0;
}
}
.user-cover:after {
/* Gradient overlay */
content: "";
position: absolute;
right: 0;
bottom: 0;
left: 0;
height: 110px;
background: linear-gradient(transparent, rgba(0, 0, 0, 0.18));
} }
.user-cover-edit { .user-cover-edit {
@ -96,62 +75,11 @@
/* Edit user /* Edit user
/* ---------------------------------------------------------- */ /* ---------------------------------------------------------- */
@media (min-width: 651px) {
.first-form-group {
margin-right: 20px;
padding-left: 40px;
}
.first-form-group input {
max-width: 100%;
}
}
.user-details-top {
position: relative;
}
@media (max-width: 650px) {
.user-details-top {
margin-top: 40px;
margin-bottom: 0;
}
}
@media (min-width: 651px) {
.user-details-top {
margin-top: -91px;
margin-bottom: 0;
padding: 0;
}
.user-details-top p {
color: #fff;
}
.user-details-top label[for="user-name"] {
color: transparent;
}
.user-details-top .user-name {
border-color: #fff;
}
}
.user-profile { .user-profile {
position: relative; position: relative;
z-index: 1; z-index: 1;
} }
@media (min-width: 651px) {
.user-profile {
padding-right: 20px;
padding-left: 143px;
}
}
@media (max-width: 650px) {
.user-profile fieldset {
padding: 0 40px;
}
}
@media (max-width: 550px) { @media (max-width: 550px) {
.user-profile fieldset { .user-profile fieldset {
padding: 0 15px; padding: 0 15px;
@ -159,7 +87,7 @@
} }
.user-profile textarea { .user-profile textarea {
min-width: 240px; min-width: 100%;
} }
@ -168,43 +96,24 @@
.user-image { .user-image {
position: absolute; position: absolute;
top: 200px;
left: 0;
z-index: 2; z-index: 2;
display: block; margin: 0;
float: left; padding: 0;
overflow: hidden; width: 100px;
margin-right: 20px; height: 100px;
margin-left: -6px; border-radius: 0 6px;
padding: 3px;
width: 126px;
height: 126px;
background: #fff;
border-radius: 100%;
text-align: center; text-align: center;
} }
@media (min-width: 651px) {
.user-image {
top: -19px;
left: -98px;
}
}
@media (max-width: 650px) {
.user-image {
top: -135px;
left: 50%;
margin-right: 0;
margin-left: -63px;
}
}
.user-image .img { .user-image .img {
display: block; display: block;
width: 120px; width: 100%;
height: 120px; height: 100%;
background-position: center center; background-position: center center;
background-size: cover; background-size: cover;
border-radius: 100%; border-radius: 0 6px;
} }
.user-image:hover .edit-user-image { .user-image:hover .edit-user-image {
@ -213,17 +122,16 @@
.edit-user-image { .edit-user-image {
position: absolute; position: absolute;
top: 3px; top: 0;
right: 3px; left: 0;
bottom: 3px; width: 100%;
left: 3px;
width: calc(100% - 6px);
background: rgba(0, 0, 0, 0.5); background: rgba(0, 0, 0, 0.5);
border-radius: 100%; border-radius: 0 6px;
color: #fff; color: #fff;
text-decoration: none; text-decoration: none;
text-transform: uppercase; text-transform: uppercase;
line-height: 120px; font-size: 12px;
line-height: 100px;
opacity: 0; opacity: 0;
transition: opacity 0.3s ease; transition: opacity 0.3s ease;
} }

View File

@ -84,37 +84,37 @@
</section> </section>
</header> </header>
<div class="view-container settings-user"> <div class="gm-main view-container settings-user">
<div class="gh-canvas">
<form class="user-profile" novalidate="novalidate" autocomplete="off" {{action (perform save) on="submit"}}>
<figure class="user-cover" style={{coverImageBackground}}> <figure class="user-cover" style={{coverImageBackground}}>
<button class="gh-btn gh-btn-default user-cover-edit" {{action "toggleUploadCoverModal"}}><span>Change Cover</span></button> <button class="gh-btn gh-btn-default user-cover-edit" {{action "toggleUploadCoverModal"}}><span>Change Cover</span></button>
{{#if showUploadCoverModal}} {{#if showUploadCoverModal}}
{{gh-fullscreen-modal "upload-image" {{gh-fullscreen-modal "upload-image"
model=(hash model=user imageProperty="coverImage") model=(hash model=user imageProperty="cover")
close=(action "toggleUploadCoverModal") close=(action "toggleUploadCoverModal")
modifier="action wide"}} modifier="action wide"}}
{{/if}} {{/if}}
</figure> </figure>
<form class="user-profile" novalidate="novalidate" autocomplete="off" {{action (perform save) on="submit"}}>
{{!-- 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"/>
<fieldset class="user-details-top">
<figure class="user-image"> <figure class="user-image">
<div id="user-image" class="img" style={{userImageBackground}}><span class="hidden">{{user.name}}"s Picture</span></div> <div id="user-image" class="img" style={{userImageBackground}}><span class="hidden">{{user.name}}"s Picture</span></div>
<button type="button" {{action "toggleUploadImageModal"}} class="edit-user-image">Edit Picture</button> <button type="button" {{action "toggleUploadImageModal"}} class="edit-user-image">Edit Picture</button>
{{#if showUploadImageModal}} {{#if showUploadImageModal}}
{{gh-fullscreen-modal "upload-image" {{gh-fullscreen-modal "upload-image"
model=(hash model=user imageProperty="profileImage") model=(hash model=user imageProperty="image")
close=(action "toggleUploadImageModal") close=(action "toggleUploadImageModal")
modifier="action wide"}} modifier="action wide"}}
{{/if}} {{/if}}
</figure> </figure>
{{!-- 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"/>
<fieldset class="user-details-bottom">
{{#gh-form-group errors=user.errors hasValidated=user.hasValidated property="name" class="first-form-group"}} {{#gh-form-group errors=user.errors hasValidated=user.hasValidated property="name" class="first-form-group"}}
<label for="user-name">Full Name</label> <label for="user-name">Full Name</label>
{{gh-input user.name id="user-name" class="user-name" placeholder="Full Name" autocorrect="off" focusOut=(action "validate" "name" target=user) update=(action (mut user.name))}} {{gh-input user.name id="user-name" class="user-name" placeholder="Full Name" autocorrect="off" focusOut=(action "validate" "name" target=user) update=(action (mut user.name))}}
@ -125,10 +125,6 @@
{{/if}} {{/if}}
{{/gh-form-group}} {{/gh-form-group}}
</fieldset>
<fieldset class="user-details-bottom">
{{#gh-form-group errors=user.errors hasValidated=user.hasValidated property="slug"}} {{#gh-form-group errors=user.errors hasValidated=user.hasValidated property="slug"}}
<label for="user-slug">Slug</label> <label for="user-slug">Slug</label>
{{gh-input slugValue class="user-name" id="user-slug" name="user" focusOut=(action (perform updateSlug slugValue)) placeholder="Slug" selectOnClick="true" autocorrect="off" update=(action (mut slugValue))}} {{gh-input slugValue class="user-name" id="user-slug" name="user" focusOut=(action (perform updateSlug slugValue)) placeholder="Slug" selectOnClick="true" autocorrect="off" update=(action (mut slugValue))}}
@ -160,7 +156,6 @@
value=model.role value=model.role
update=(action "changeRole") update=(action "changeRole")
}} }}
{{inline-svg "arrow-down-small"}}
</span> </span>
<p>What permissions should this user have?</p> <p>What permissions should this user have?</p>
</div> </div>
@ -235,10 +230,7 @@
{{/gh-form-group}} {{/gh-form-group}}
<div class="form-group"> <div class="form-group">
{{gh-task-button "Change Password" {{gh-task-button "Change Password" class="gh-btn gh-btn-red gh-btn-icon button-change-password" task=user.saveNewPassword}}
class="gh-btn gh-btn-icon button-change-password"
idleClass="gh-btn-red"
task=user.saveNewPassword}}
</div> </div>
</fieldset> </fieldset>
</form> {{! change password form }} </form> {{! change password form }}
@ -250,5 +242,7 @@
<p class="gh-box gh-box-info">{{inline-svg "lock"}} To change your login details please visit <a href="https://my.ghost.org/account" target="_blank">https://my.ghost.org/account</a></p> <p class="gh-box gh-box-info">{{inline-svg "lock"}} To change your login details please visit <a href="https://my.ghost.org/account" target="_blank">https://my.ghost.org/account</a></p>
</div> </div>
{{/if}} {{/if}}
</div>
</div> </div>
</section> </section>

View File

@ -469,22 +469,22 @@ describe('Acceptance: Team', function () {
await visit('/team/test-1'); await visit('/team/test-1');
expect(currentURL(), 'currentURL').to.equal('/team/test-1'); expect(currentURL(), 'currentURL').to.equal('/team/test-1');
expect(find('.user-details-top .first-form-group input.user-name').val(), 'current user name').to.equal('Test User'); expect(find('.user-details-bottom .first-form-group input.user-name').val(), 'current user name').to.equal('Test User');
// test empty user name // test empty user name
await fillIn('.user-details-top .first-form-group input.user-name', ''); await fillIn('.user-details-bottom .first-form-group input.user-name', '');
await triggerEvent('.user-details-top .first-form-group input.user-name', 'blur'); await triggerEvent('.user-details-bottom .first-form-group input.user-name', 'blur');
expect(find('.user-details-top .first-form-group').hasClass('error'), 'username input is in error state with blank input').to.be.true; expect(find('.user-details-bottom .first-form-group').hasClass('error'), 'username input is in error state with blank input').to.be.true;
// test too long user name // test too long user name
await fillIn('.user-details-top .first-form-group input.user-name', new Array(160).join('a')); await fillIn('.user-details-bottom .first-form-group input.user-name', new Array(160).join('a'));
await triggerEvent('.user-details-top .first-form-group input.user-name', 'blur'); await triggerEvent('.user-details-bottom .first-form-group input.user-name', 'blur');
expect(find('.user-details-top .first-form-group').hasClass('error'), 'username input is in error state with too long input').to.be.true; expect(find('.user-details-bottom .first-form-group').hasClass('error'), 'username input is in error state with too long input').to.be.true;
// reset name field // reset name field
await fillIn('.user-details-top .first-form-group input.user-name', 'Test User'); await fillIn('.user-details-bottom .first-form-group input.user-name', 'Test User');
expect(find('.user-details-bottom input[name="user"]').val(), 'slug value is default').to.equal('test-1'); expect(find('.user-details-bottom input[name="user"]').val(), 'slug value is default').to.equal('test-1');
@ -501,7 +501,7 @@ describe('Acceptance: Team', function () {
await fillIn('.user-details-bottom input[name="email"]', 'thisisnotanemail'); await fillIn('.user-details-bottom input[name="email"]', 'thisisnotanemail');
await triggerEvent('.user-details-bottom input[name="email"]', 'blur'); await triggerEvent('.user-details-bottom input[name="email"]', 'blur');
expect(find('.user-details-bottom .form-group:nth-of-type(2)').hasClass('error'), 'email input should be in error state with invalid email').to.be.true; expect(find('.user-details-bottom .form-group:nth-of-type(3)').hasClass('error'), 'email input should be in error state with invalid email').to.be.true;
await fillIn('.user-details-bottom input[name="email"]', 'test@example.com'); await fillIn('.user-details-bottom input[name="email"]', 'test@example.com');
await fillIn('#user-location', new Array(160).join('a')); await fillIn('#user-location', new Array(160).join('a'));

View File

@ -3,8 +3,8 @@ import {describe, it} from 'mocha';
import {countCharacters} from 'ghost-admin/helpers/gh-count-characters'; import {countCharacters} from 'ghost-admin/helpers/gh-count-characters';
describe('Unit: Helper: gh-count-characters', function() { describe('Unit: Helper: gh-count-characters', function() {
let defaultStyle = 'color: rgb(158, 157, 149);'; let defaultStyle = 'color: rgb(115, 138, 148);';
let errorStyle = 'color: rgb(226, 84, 64);'; let errorStyle = 'color: rgb(240, 82, 48);';
it('counts remaining chars', function() { it('counts remaining chars', function() {
let result = countCharacters(['test']); let result = countCharacters(['test']);