Browse Source

Profile page.

pull/65/head
Sebastian Stehle 9 years ago
parent
commit
beb2a508bd
  1. 10
      src/Squidex.Read.MongoDb/Users/MongoUserStore.cs
  2. 3
      src/Squidex.Read/Users/UserManagerExtensions.cs
  3. 32
      src/Squidex/Controllers/UI/Account/AccountController.cs
  4. 21
      src/Squidex/Controllers/UI/Error/ErrorController.cs
  5. 24
      src/Squidex/Controllers/UI/Profile/ChangePasswordModel.cs
  6. 22
      src/Squidex/Controllers/UI/Profile/ChangeProfileModel.cs
  7. 123
      src/Squidex/Controllers/UI/Profile/ProfileController.cs
  8. 23
      src/Squidex/Controllers/UI/Profile/ProfileVM.cs
  9. 21
      src/Squidex/Controllers/UI/Profile/SetPasswordModel.cs
  10. 8
      src/Squidex/Startup.cs
  11. 9
      src/Squidex/Views/Account/Error.cshtml
  12. 62
      src/Squidex/Views/Account/Login.cshtml
  13. 9
      src/Squidex/Views/Error/Error.cshtml
  14. 155
      src/Squidex/Views/Profile/Profile.cshtml
  15. 2
      src/Squidex/Views/_Layout.cshtml
  16. 4
      src/Squidex/app/features/administration/pages/users/users-page.component.html
  17. 23
      src/Squidex/app/framework/angular/popup-link.directive.ts
  18. 1
      src/Squidex/app/framework/declarations.ts
  19. 3
      src/Squidex/app/framework/module.ts
  20. 4
      src/Squidex/app/shell/pages/internal/profile-menu.component.html
  21. 6
      src/Squidex/app/shell/pages/internal/profile-menu.component.ts
  22. 10
      src/Squidex/app/theme/_forms.scss
  23. 33
      src/Squidex/app/theme/_static.scss

10
src/Squidex.Read.MongoDb/Users/MongoUserStore.cs

@ -146,11 +146,6 @@ namespace Squidex.Read.MongoDb.Users
return innerStore.SetPasswordHashAsync((WrappedIdentityUser)user, passwordHash, cancellationToken);
}
public Task<bool> HasPasswordAsync(IUser user, CancellationToken cancellationToken)
{
return innerStore.HasPasswordAsync((WrappedIdentityUser)user, cancellationToken);
}
public Task AddToRoleAsync(IUser user, string roleName, CancellationToken cancellationToken)
{
return innerStore.AddToRoleAsync((WrappedIdentityUser)user, roleName, cancellationToken);
@ -325,5 +320,10 @@ namespace Squidex.Read.MongoDb.Users
{
return innerStore.GetTokenAsync((WrappedIdentityUser)user, loginProvider, name, cancellationToken);
}
public Task<bool> HasPasswordAsync(IUser user, CancellationToken cancellationToken)
{
return Task.FromResult(!string.IsNullOrWhiteSpace(((WrappedIdentityUser)user).PasswordHash));
}
}
}

3
src/Squidex.Read/Users/UserManagerExtensions.cs

@ -11,7 +11,6 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Squidex.Core.Identity;
using Squidex.Infrastructure;
// ReSharper disable ImplicitlyCapturedClosure
@ -83,7 +82,7 @@ namespace Squidex.Read.Users
if (!string.IsNullOrWhiteSpace(displayName))
{
user.SetClaim(SquidexClaimTypes.SquidexDisplayName, displayName);
user.SetDisplayName(displayName);
}
await DoChecked(() => userManager.UpdateAsync(user), "Cannot update user.");

32
src/Squidex/Controllers/UI/Account/AccountController.cs

@ -9,6 +9,7 @@
using System;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Security;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
@ -59,20 +60,6 @@ namespace Squidex.Controllers.UI.Account
this.identityOptions = identityOptions;
this.signInManager = signInManager;
}
[HttpGet]
[Route("account/forbidden")]
public IActionResult Forbidden()
{
return View("Error");
}
[HttpGet]
[Route("account/accessdenied")]
public IActionResult AccessDenied()
{
return View("LockedOut");
}
[HttpGet]
[Route("client-callback-silent/")]
@ -89,15 +76,22 @@ namespace Squidex.Controllers.UI.Account
}
[HttpGet]
[Route("account/logout-completed/")]
public IActionResult LogoutCompleted()
[Route("account/forbidden")]
public IActionResult Forbidden()
{
return View();
throw new SecurityException("User is not allowed to login.");
}
[HttpGet]
[Route("account/accessdenied")]
public IActionResult AccessDenied()
{
return View("LockedOut");
}
[HttpGet]
[Route("account/error/")]
public IActionResult Error()
[Route("account/logout-completed/")]
public IActionResult LogoutCompleted()
{
return View();
}

21
src/Squidex/Controllers/UI/Error/ErrorController.cs

@ -0,0 +1,21 @@
// ==========================================================================
// ErrorController.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using Microsoft.AspNetCore.Mvc;
namespace Squidex.Controllers.UI.Error
{
public class ErrorController : Controller
{
[Route("error")]
public IActionResult Error()
{
return View();
}
}
}

24
src/Squidex/Controllers/UI/Profile/ChangePasswordModel.cs

@ -0,0 +1,24 @@
// ==========================================================================
// ChangePasswordModel.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.ComponentModel.DataAnnotations;
namespace Squidex.Controllers.UI.Profile
{
public class ChangePasswordModel
{
[Required(ErrorMessage = "Old Password is required.")]
public string OldPassword { get; set; }
[Required(ErrorMessage = "Password is required.")]
public string Password { get; set; }
[Compare(nameof(Password), ErrorMessage = "Passwords must be identitical.")]
public string PasswordConfirm { get; set; }
}
}

22
src/Squidex/Controllers/UI/Profile/ChangeProfileModel.cs

@ -0,0 +1,22 @@
// ==========================================================================
// ChangeProfileModel.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.ComponentModel.DataAnnotations;
namespace Squidex.Controllers.UI.Profile
{
public class ChangeProfileModel
{
[Required(ErrorMessage = "Email is required.")]
[EmailAddress(ErrorMessage = "Email is not valid.")]
public string Email { get; set; }
[Required(ErrorMessage = "DisplayName is required.")]
public string DisplayName { get; set; }
}
}

123
src/Squidex/Controllers/UI/Profile/ProfileController.cs

@ -0,0 +1,123 @@
// ==========================================================================
// ProfileController.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
using NSwag.Annotations;
using Squidex.Config.Identity;
using Squidex.Infrastructure.Reflection;
using Squidex.Read.Users;
namespace Squidex.Controllers.UI.Profile
{
[Authorize]
[SwaggerIgnore]
public class ProfileController : Controller
{
private readonly UserManager<IUser> userManager;
private readonly IOptions<MyIdentityOptions> identityOptions;
public ProfileController(UserManager<IUser> userManager, IOptions<MyIdentityOptions> identityOptions)
{
this.userManager = userManager;
this.identityOptions = identityOptions;
}
[HttpGet]
[Route("/account/profile")]
public async Task<IActionResult> Profile(string successMessage = null)
{
var user = await userManager.GetUserAsync(User);
ViewBag.SuccessMessage = successMessage;
return View(await GetProfileVM(user));
}
[HttpPost]
[Route("/account/profile")]
public Task<IActionResult> Profile(ChangeProfileModel model)
{
return MakeChangeAsync(async user =>
{
user.UpdateEmail(model.Email);
user.SetDisplayName(model.DisplayName);
return await userManager.UpdateAsync(user);
}, "Account updated successfully. Please logout and login again to see the changes.");
}
[HttpPost]
[Route("/account/setpassword")]
public Task<IActionResult> SetPassword(SetPasswordModel model)
{
return MakeChangeAsync(user => userManager.AddPasswordAsync(user, model.Password),
"Password set successfully.");
}
[HttpPost]
[Route("/account/changepassword")]
public Task<IActionResult> ChangePassword(ChangePasswordModel model)
{
return MakeChangeAsync(user => userManager.ChangePasswordAsync(user, model.OldPassword, model.Password),
"Password changed successfully.");
}
private async Task<IActionResult> MakeChangeAsync(Func<IUser, Task<IdentityResult>> action, string successMessage, ChangeProfileModel model = null)
{
var user = await userManager.GetUserAsync(User);
if (!ModelState.IsValid)
{
return View("Profile", await GetProfileVM(user, model));
}
try
{
var result = await action(user);
if (result.Succeeded)
{
return RedirectToAction(nameof(Profile), new { successMessage });
}
ViewBag.ErrorMessage = string.Join(". ", result.Errors.Select(x => x.Description));
}
catch
{
ViewBag.ErrorMessage = "An unexpected exception occurred.";
}
return View("Profile", await GetProfileVM(user, model));
}
private async Task<ProfileVM> GetProfileVM(IUser user, ChangeProfileModel model = null)
{
var result = new ProfileVM
{
Email = user.Email,
DisplayName = user.DisplayName(),
PictureUrl = user.PictureUrl(),
HasPassword = await userManager.HasPasswordAsync(user),
HasPasswordAuth = identityOptions.Value.AllowPasswordAuth
};
if (model != null)
{
SimpleMapper.Map(model, result);
}
return result;
}
}
}

23
src/Squidex/Controllers/UI/Profile/ProfileVM.cs

@ -0,0 +1,23 @@
// ==========================================================================
// ProfileVM.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
namespace Squidex.Controllers.UI.Profile
{
public sealed class ProfileVM
{
public string Email { get; set; }
public string DisplayName { get; set; }
public string PictureUrl { get; set; }
public bool HasPassword { get; set; }
public bool HasPasswordAuth { get; set; }
}
}

21
src/Squidex/Controllers/UI/Profile/SetPasswordModel.cs

@ -0,0 +1,21 @@
// ==========================================================================
// SetPasswordModel.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.ComponentModel.DataAnnotations;
namespace Squidex.Controllers.UI.Profile
{
public class SetPasswordModel
{
[Required(ErrorMessage = "Password is required.")]
public string Password { get; set; }
[Compare(nameof(Password), ErrorMessage = "Passwords must be identitical.")]
public string PasswordConfirm { get; set; }
}
}

8
src/Squidex/Startup.cs

@ -36,7 +36,8 @@ namespace Squidex
{
"/client-callback-popup",
"/client-callback-silent",
"/account"
"/account",
"/error"
};
private IConfigurationRoot Configuration { get; }
@ -137,6 +138,10 @@ namespace Squidex
{
identityApp.UseDeveloperExceptionPage();
}
else
{
identityApp.UseExceptionHandler("/error");
}
identityApp.UseMyIdentity();
identityApp.UseMyIdentityServer();
@ -177,7 +182,6 @@ namespace Squidex
{
if (Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseWebpackProxy();
app.Use((context, next) =>

9
src/Squidex/Views/Account/Error.cshtml

@ -1,9 +0,0 @@
@{
ViewBag.Title = "Login failed";
}
<h1 class="splash-h1">Login failed</h1>
<p class="splash-text">
We are really sorry, something went wrong when you tried to login.
</p>

62
src/Squidex/Views/Account/Login.cshtml

@ -9,29 +9,31 @@
ViewBag.Title = type;
}
<div class="login">
<img class="login-logo" src="~/images/logo-small.png" />
<div class="row">
<div class="col-6">
@if (Model.IsLogin)
{
<a class="login-headline active" asp-controller="Account" asp-action="Login" asp-route-returnurl="@Model.ReturnUrl">Login</a>
}
else
{
<a class="login-headline" asp-controller="Account" asp-action="Login" asp-route-returnurl="@Model.ReturnUrl">Login</a>
}
</div>
<div class="col-6 login-headline">
@if (!Model.IsLogin)
{
<a class="login-headline active" asp-controller="Account" asp-action="Signup" asp-route-returnurl="@Model.ReturnUrl">Signup</a>
}
else
{
<a class="login-headline" asp-controller="Account" asp-action="Signup" asp-route-returnurl="@Model.ReturnUrl">Signup</a>
}
<div class="profile">
<img class="profile-logo" src="~/images/logo-small.png" />
<div class="container">
<div class="row">
<div class="col-6">
@if (Model.IsLogin)
{
<a class="profile-headline active" asp-controller="Account" asp-action="Login" asp-route-returnurl="@Model.ReturnUrl">Login</a>
}
else
{
<a class="profile-headline" asp-controller="Account" asp-action="Login" asp-route-returnurl="@Model.ReturnUrl">Login</a>
}
</div>
<div class="col-6 profile-headline">
@if (!Model.IsLogin)
{
<a class="profile-headline active" asp-controller="Account" asp-action="Signup" asp-route-returnurl="@Model.ReturnUrl">Signup</a>
}
else
{
<a class="profile-headline" asp-controller="Account" asp-action="Signup" asp-route-returnurl="@Model.ReturnUrl">Signup</a>
}
</div>
</div>
</div>
@ -50,8 +52,8 @@
@if (Model.HasPasswordAndExternal)
{
<div class="login-separator">
<div class="login-separator-text">OR</div>
<div class="profile-separator">
<div class="profile-separator-text">OR</div>
</div>
}
@ -66,11 +68,11 @@
<form asp-controller="Account" asp-action="Login" asp-route-returnurl="@Model.ReturnUrl" method="post">
<div class="form-group">
<input type="email" class="form-control" name="email" id="email" placeholder="Enter Email">
<input type="email" class="form-control" name="email" id="email" placeholder="Enter Email" />
</div>
<div class="form-group">
<input type="password" class="form-control" name="password" id="password" placeholder="Enter Password">
<input type="password" class="form-control" name="password" id="password" placeholder="Enter Password" />
</div>
<button type="submit" class="btn btn-block btn-primary">@type</button>
@ -78,19 +80,19 @@
}
else
{
<div class="login-password-signup text-center">Ask your administrator to create an account.</div>
<div class="profile-password-signup text-center">Ask your administrator to create an account.</div>
}
}
@if (Model.IsLogin)
{
<p class="login-footer">
<p class="profile-footer">
No account yet? <a asp-controller="Account" asp-action="Signup" asp-route-returnurl="@Model.ReturnUrl">Click here to signup</a>
</p>
}
else
{
<p class="login-footer">
<p class="profile-footer">
Already registered? <a asp-controller="Account" asp-action="Login" asp-route-returnurl="@Model.ReturnUrl">Click here to login</a>
</p>
}

9
src/Squidex/Views/Error/Error.cshtml

@ -0,0 +1,9 @@
@{
ViewBag.Title = "Operation failed";
}
<h1 class="splash-h1">Operation failed</h1>
<p class="splash-text">
We are really sorry that something went wrong.
</p>

155
src/Squidex/Views/Profile/Profile.cshtml

@ -0,0 +1,155 @@
@using System.Collections.Generic
@using Microsoft.AspNetCore.Http
@using Microsoft.AspNetCore.Http.Authentication
@model Squidex.Controllers.UI.Profile.ProfileVM
@{
ViewBag.Title = "Profile";
}
<div class="profile profile-lg">
<img class="profile-logo" src="~/images/logo-small.png" />
<div>
<h4 class="profile-title">Welcome, @Model.DisplayName</h4>
</div>
@if (!string.IsNullOrWhiteSpace(ViewBag.SuccessMessage))
{
<div class="form-success">
@ViewBag.SuccessMessage
</div>
}
@if (!string.IsNullOrWhiteSpace(ViewBag.ErrorMessage))
{
<div class="form-error">
@ViewBag.ErrorMessage
</div>
}
<div class="row">
<div class="col profile-picture-col">
<img class="profile-picture" src="@Model.PictureUrl" />
</div>
<div class="col">
<form class="profile-form" asp-controller="Profile" asp-action="Profile" method="post">
<div class="form-group">
<label for="email">Email</label>
@if (ViewContext.ViewData.ModelState["Email"]?.ValidationState == Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Invalid)
{
<div class="errors-box">
<span asp-validation-for="Email" class="errors"></span>
</div>
}
<input type="email" ap class="form-control" asp-for="Email" name="email" id="email" />
</div>
<div class="form-group">
<label for="displayName">Display Name</label>
@if (ViewContext.ViewData.ModelState["DisplayName"]?.ValidationState == Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Invalid)
{
<div class="errors-box">
<span asp-validation-for="DisplayName" class="errors"></span>
</div>
}
<input type="text" class="form-control" asp-for="DisplayName" name="displayName" id="displayName"/>
</div>
<button type="submit" class="btn btn-primary">Save</button>
</form>
</div>
</div>
@if (Model.HasPasswordAuth)
{
<h5 class="profile-section">Password</h5>
@if (Model.HasPassword)
{
<form class="profile-form" asp-controller="Profile" asp-action="ChangePassword" method="post">
<div class="form-group">
<label for="oldPassword">Old Password</label>
@if (ViewContext.ViewData.ModelState["OldPassword"]?.ValidationState == Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Invalid)
{
<div class="errors-box">
<span class="errors">@Html.ValidationMessage("OldPassword")</span>
</div>
}
<input type="password" class="form-control" name="oldPassword" id="oldPassword"/>
</div>
<div class="form-group">
<label for="password">Password</label>
@if (ViewContext.ViewData.ModelState["Password"]?.ValidationState == Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Invalid)
{
<div class="errors-box">
<span class="errors">@Html.ValidationMessage("Password")</span>
</div>
}
<input type="password" class="form-control" name="password" id="password"/>
</div>
<div class="form-group">
<label for="passwordConfirm">Confirm</label>
@if (ViewContext.ViewData.ModelState["PasswordConfirm"]?.ValidationState == Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Invalid)
{
<div class="errors-box">
<span class="errors">@Html.ValidationMessage("PasswordConfirm")</span>
</div>
}
<input type="password" class="form-control" name="passwordConfirm" id="passwordConfirm"/>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">Change Password</button>
</div>
</form>
}
else
{
<form class="profile-form" asp-controller="Profile" asp-action="SetPassword" method="post">
<div class="form-group">
<label for="password">Password</label>
@if (ViewContext.ViewData.ModelState["Password"]?.ValidationState == Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Invalid)
{
<div class="errors-box">
<span class="errors">@Html.ValidationMessage("Password")</span>
</div>
}
<input type="password" class="form-control" name="password" id="password"/>
</div>
<div class="form-group">
<label for="passwordConfirm">Confirm</label>
@if (ViewContext.ViewData.ModelState["PasswordConfirm"]?.ValidationState == Microsoft.AspNetCore.Mvc.ModelBinding.ModelValidationState.Invalid)
{
<div class="errors-box">
<span class="errors">@Html.ValidationMessage("PasswordConfirm")</span>
</div>
}
<input type="password" class="form-control" name="passwordConfirm" id="passwordConfirm"/>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">Set Password</button>
</div>
</form>
}
}
</div>

2
src/Squidex/Views/_Layout.cshtml

@ -6,7 +6,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Squidex - @ViewBag.Title</title>
<title>@ViewBag.Title - Squidex Headless CMS</title>
<environment names="Production">
<link rel="stylesheet" asp-append-version="true" href="~/build/app.css" />

4
src/Squidex/app/features/administration/pages/users/users-page.component.html

@ -68,10 +68,10 @@
</td>
<td class="col-right">
<span *ngIf="user.id !== currentUserId">
<button class="btn btn-link" (click)="lock(user.id)" *ngIf="!user.isLocked" title="Lock User">
<button class="btn btn-link" (click)="lock(user.id); $event.stopPropagation();" *ngIf="!user.isLocked" title="Lock User">
<i class="icon icon-unlocked"></i>
</button>
<button class="btn btn-link" (click)="unlock(user.id)" *ngIf="user.isLocked" title="Unlock User">
<button class="btn btn-link" (click)="unlock(user.id); $event.stopPropagation();" *ngIf="user.isLocked" title="Unlock User">
<i class="icon icon-lock"></i>
</button>
</span>

23
src/Squidex/app/framework/angular/popup-link.directive.ts

@ -0,0 +1,23 @@
/*
* Squidex Headless CMS
*
* @license
* Copyright (c) Sebastian Stehle. All rights reserved
*/
import { Directive, HostListener, Input } from '@angular/core';
@Directive({
selector: '[sqxPopupLink]'
})
export class PopupLinkDirective {
@Input('sqxPopupLink')
public url: string;
@HostListener('click')
public onClick(): boolean {
window.open(this.url, '_target', 'location=no,toolbar=no,width=500,height=500,left=100,top=100;');
return false;
}
}

1
src/Squidex/app/framework/declarations.ts

@ -29,6 +29,7 @@ export * from './angular/name.pipe';
export * from './angular/panel.component';
export * from './angular/panel-container.directive';
export * from './angular/parent-link.directive';
export * from './angular/popup-link.directive';
export * from './angular/progress-bar.component';
export * from './angular/rich-editor.component';
export * from './angular/scroll-active.directive';

3
src/Squidex/app/framework/module.ts

@ -43,6 +43,7 @@ import {
PanelComponent,
PanelService,
ParentLinkDirective,
PopupLinkDirective,
ProgressBarComponent,
ResourceLoaderService,
RichEditorComponent,
@ -94,6 +95,7 @@ import {
PanelContainerDirective,
PanelComponent,
ParentLinkDirective,
PopupLinkDirective,
ProgressBarComponent,
RichEditorComponent,
ScrollActiveDirective,
@ -133,6 +135,7 @@ import {
PanelContainerDirective,
PanelComponent,
ParentLinkDirective,
PopupLinkDirective,
ProgressBarComponent,
RichEditorComponent,
ScrollActiveDirective,

4
src/Squidex/app/shell/pages/internal/profile-menu.component.html

@ -11,6 +11,10 @@
Administration
</a>
<a class="dropdown-item" [sqxPopupLink]="profileUrl">
Profile
</a>
<a class="dropdown-item" (click)="logout()">
Logout
</a>

6
src/Squidex/app/shell/pages/internal/profile-menu.component.ts

@ -9,6 +9,7 @@ import { Component, OnDestroy, OnInit } from '@angular/core';
import { Subscription } from 'rxjs';
import {
ApiUrlConfig,
AuthService,
fadeAnimation,
ModalView
@ -32,8 +33,11 @@ export class ProfileMenuComponent implements OnInit, OnDestroy {
public isAdmin = false;
public profileUrl = this.apiUrl.buildUrl('/identity-server/account/profile');
constructor(
private readonly auth: AuthService
private readonly auth: AuthService,
private readonly apiUrl: ApiUrlConfig
) {
}

10
src/Squidex/app/theme/_forms.scss

@ -66,7 +66,8 @@ select {
color: lighten($color-text, 30%);
}
.form-error {
.form-error,
.form-success {
@include border-radius(4px);
color: $color-dark-foreground;
margin-top: .25rem;
@ -74,9 +75,16 @@ select {
font-size: .9rem;
font-weight: normal;
padding: .5rem;
}
.form-error {
background: $color-theme-error;
}
.form-success {
background: $color-theme-green-dark;
}
.form-group {
&:last-child {
margin-bottom: 0;

33
src/Squidex/app/theme/_static.scss

@ -9,7 +9,7 @@ noscript {
margin-bottom: 20px;
}
.login {
.profile {
& {
max-width: 20rem;
margin: 0 auto;
@ -38,10 +38,30 @@ noscript {
@include absolute(1rem, auto, auto, 1rem);
}
&-title {
margin-bottom: 2rem;
}
&-section {
margin-top: 3rem;
}
&-lg {
max-width: 40rem;
}
&-password-signup {
color: $color-empty;
}
&-picture-col {
max-width: 7rem;
}
&-picture {
max-width: 100%;
}
&-footer {
font-size: .8rem;
font-weight: normal;
@ -100,4 +120,15 @@ noscript {
font-size: 30px;
font-weight: lighter;
}
}
@media (max-width: 400px) {
.profile-picture-col {
min-width: 100%;
margin-bottom: 1rem;
}
.profile-picture {
height: 5rem;
}
}
Loading…
Cancel
Save