Browse Source

feat(admin-ui): Login UI refresh (#1862)

David Höck 3 years ago
parent
commit
72febce692

+ 1 - 0
packages/admin-ui-plugin/src/plugin.ts

@@ -246,6 +246,7 @@ export class AdminUiPlugin implements NestModule {
                 'hideVersion',
                 AdminUiPlugin.options.adminUiConfig?.hideVersion || false,
             ),
+            loginImage: AdminUiPlugin.options.adminUiConfig?.loginImage,
             cancellationReasons: propOrDefault('cancellationReasons', undefined),
         };
     }

+ 68 - 52
packages/admin-ui/src/lib/login/src/components/login/login.component.html

@@ -1,56 +1,72 @@
 <div class="login-wrapper">
-    <form class="login">
-        <label class="title">
-            <img src="assets/logo-300px.png" />
-            <span *ngIf="!hideVendureBranding">vendure</span>
-        </label>
-        <div class="login-group">
-            <input
-                class="username"
-                type="text"
-                name="username"
-                id="login_username"
-                [(ngModel)]="username"
-                [placeholder]="'common.username' | translate"
-            />
-            <input
-                class="password"
-                name="password"
-                type="password"
-                id="login_password"
-                [(ngModel)]="password"
-                [placeholder]="'common.password' | translate"
-            />
-            <clr-alert [clrAlertType]="'danger'"  [clrAlertClosable]="false" [class.visible]="errorMessage" class="login-error">
-                <clr-alert-item>
-                    <span class="alert-text">
-                        {{ errorMessage }}
-                    </span>
-                </clr-alert-item>
-            </clr-alert>
-            <clr-checkbox-wrapper>
-                <input
-                    type="checkbox"
-                    clrCheckbox
-                    id="rememberme"
-                    name="rememberme"
-                    [(ngModel)]="rememberMe"
-                />
-                <label>{{ 'common.remember-me' | translate }}</label>
-            </clr-checkbox-wrapper>
-            <button
-                type="submit"
-                class="btn btn-primary"
-                (click)="logIn()"
-                [disabled]="!username || !password"
-            >
-                {{ 'common.login' | translate }}
-            </button>
+    <div class="login-wrapper-inner">
+        <div class="login-wrapper-image">
+            <div class="login-wrapper-image-content">
+                <div class="login-wrapper-image-title">
+                    {{ 'common.login-image-title' | translate }}
+                </div>
+                <div class="login-wrapper-image-copyright">
+                    <p *ngIf="imageCreator" class="creator">Photo by  {{ imageCreator }} on Unsplash</p>
+                    <p *ngIf="imageLocation" class="location">{{ imageLocation }}</p>
+                </div>
+            </div>
+            <img *ngIf="imageUrl" [src]="imageUrl" [alt]="imageUrl">
         </div>
-        <div class="version">
-            <span *ngIf="brand">{{ brand }} <span *ngIf="!hideVendureBranding || !hideVersion">-</span></span>
-            <span *ngIf="!hideVendureBranding">vendure</span>
-            <span *ngIf="!hideVersion">v{{ version }}</span>
+        <div class="login-wrapper-form">
+            <p class="login-title">
+                {{ 'common.login-title' | translate }}
+            </p>
+            <form class="login-form">
+                <div class="login-group">
+                    <input
+                        class="username"
+                        type="text"
+                        name="username"
+                        id="login_username"
+                        [(ngModel)]="username"
+                        [placeholder]="'common.username' | translate"
+                    />
+                    <input
+                        class="password"
+                        name="password"
+                        type="password"
+                        id="login_password"
+                        [(ngModel)]="password"
+                        [placeholder]="'common.password' | translate"
+                    />
+                    <clr-alert [clrAlertType]="'danger'"  [clrAlertClosable]="false" [class.visible]="errorMessage" class="login-error">
+                        <clr-alert-item>
+                            <span class="alert-text">
+                                {{ errorMessage }}
+                            </span>
+                        </clr-alert-item>
+                    </clr-alert>
+                    <clr-checkbox-wrapper>
+                        <input
+                            type="checkbox"
+                            clrCheckbox
+                            id="rememberme"
+                            name="rememberme"
+                            [(ngModel)]="rememberMe"
+                        />
+                        <label>{{ 'common.remember-me' | translate }}</label>
+                    </clr-checkbox-wrapper>
+                    <button
+                        type="submit"
+                        class="btn btn-primary"
+                        (click)="logIn()"
+                        [disabled]="!username || !password"
+                    >
+                        {{ 'common.login' | translate }}
+                    </button>
+                </div>
+                <div class="version">
+                    <span *ngIf="brand">{{ brand }} <span *ngIf="!hideVendureBranding || !hideVersion">-</span></span>
+                    <span *ngIf="!hideVendureBranding">vendure</span>
+                    <span *ngIf="!hideVersion">v{{ version }}</span>
+                </div>
+            </form>
         </div>
-    </form>
+        <img class="login-wrapper-logo" src="assets/logo-300px.png" />
+    </div>
 </div>

+ 134 - 39
packages/admin-ui/src/lib/login/src/components/login/login.component.scss

@@ -1,60 +1,155 @@
 @import 'variables';
 
 .login-wrapper {
-    background-image: linear-gradient(135deg, var(--color-login-gradient-top), var(--color-login-gradient-bottom)),
-    var(--login-page-bg);
-    background-blend-mode: screen;
-    background-repeat: repeat;
-    background-size: auto;
-    background-position: initial;
+    background: #f0f2f5;
+    background-image: none;
+    height: 100vh;
+    display: flex;
+    align-items: center;
     justify-content: center;
-}
+    padding: 20px;
 
-@media screen and (max-width: $breakpoint-small) {
-    .login-wrapper {
-        justify-content: center;
-        background-color: transparent;
-        .login {
-            margin: 20px;
-            padding: 24px;
+    .login-wrapper-inner {
+        background: #fff;
+        width: 1120px;
+        height: 590px;
+        display: flex;
+        justify-content: flex-start;
+        align-items: stretch;
+        position: relative;
+        border-radius: 3px;
+        overflow: hidden;
+
+        @media (max-width: $breakpoint-medium) {
+            flex-direction: column;
+            height: auto;
+            width: 100%
         }
-    }
-}
 
-.login {
-    margin: 5vh 0;
-    border-radius: 6px;
-    min-height: calc(100vh - 10vh);
-    max-height: 800px;
-}
+        .login-wrapper-image {
+            height: 100%;
+            flex-grow: 1;
+            position: relative;
 
-.title {
-    display: flex;
-    flex-direction: column;
-    align-items: center;
-    text-align: center;
-    margin-top: 8vh;
+            @media (max-width: $breakpoint-medium) {
+                height: 300px;
+            }
+    
+            img {
+                display: block;
+                width: 100%;
+                height: 100%;
+                object-fit: cover;
+                object-position: center;
+                position: relative;
+                z-index: 1;
+            }
 
-    img {
-        max-width: 100%;
-        width: 150px;
-    }
-    span {
-        padding-top: 12px;
-        font-weight: bold;
-        color: var(--color-primary-500);
-        font-size: 38px;
+            .login-wrapper-image-content {
+                width: 100%;
+                height: 100%;
+                position: absolute;
+                left: 0;
+                bottom: 0;
+                z-index: 10;
+                background: rgb(2,0,36);
+                background: linear-gradient(180deg, rgba(2,0,36,0) 0%, rgba(0,0,0,0.75) 100%);
+                display: flex;
+                flex-direction: column;
+                align-items: flex-start;
+                justify-content: flex-end;
+                padding: 30px;
+
+                .login-wrapper-image-title {
+                    font-size: 1.6rem;
+                    font-weight: bold;
+                    color: white;
+                    margin-bottom: 20px;
+
+                    @media (max-width: $breakpoint-medium) {
+                        font-size: 1.2rem;
+                    }
+                }
+
+                .login-wrapper-image-copyright {
+                    opacity: 0.8;
+                    p {
+                        font-size: 0.6rem;
+                        color: white;
+                        margin: 0 !important;
+                    }
+                }
+            }
+        }
+
+        .login-wrapper-form {
+            height: 100%;
+            width: 400px;
+            padding: 40px;
+            display: flex;
+            flex-direction: column;
+            align-items: stretch;
+            justify-content: center;
+            box-shadow: 0px 20px 25px rgba(0,0,0,0.1);
+            overflow: hidden;
+            border-radius: 5px;
+            flex-shrink: 0;
+
+            @media (max-width: $breakpoint-medium) {
+                height: auto;
+                width: 100%;
+                padding: 20px;
+            }
+
+            .login-title {
+                font-weight: bold;
+                font-size: 1.2rem;
+                margin-bottom: 20px;
+                color: #afafaf; 
+            }
+
+            .login-group {
+
+                input.username,
+                input.password {
+                    display: block;
+                    width: 100%;
+                    margin-bottom: 15px;
+                    padding: 12px 16px !important;
+                    background: #fff;
+                    font-size: 14px;
+                    line-height: 22px;
+                    color: #52667a;
+                    outline: none;
+                    -webkit-appearance: none;
+                }
+
+                .btn {
+                    width: 100% !important;
+                    margin-top: 20px !important
+                }
+            }
+        }
+
+        .login-wrapper-logo {
+            width: 60px;
+            height: auto;
+            position: absolute;
+            right: 20px;
+            top: 20px;
+        }
     }
 }
 
 .version {
     flex: 1;
+    flex-grow: 1;
     display: flex;
     align-items: flex-end;
     justify-content: center;
     color: var(--color-grey-300);
 
-    span + span {
+    span+span {
         margin-left: 5px;
     }
 }
@@ -95,4 +190,4 @@
     60% {
         transform: translate3d(4px, 0, 0);
     }
-}
+}

+ 30 - 1
packages/admin-ui/src/lib/login/src/components/login/login.component.ts

@@ -1,3 +1,4 @@
+import { HttpClient, HttpParams } from '@angular/common/http';
 import { Component } from '@angular/core';
 import { Router } from '@angular/router';
 import { ADMIN_UI_VERSION, AuthService, AUTH_REDIRECT_PARAM, getAppConfig } from '@vendure/admin-ui/core';
@@ -16,8 +17,18 @@ export class LoginComponent {
     brand = getAppConfig().brand;
     hideVendureBranding = getAppConfig().hideVendureBranding;
     hideVersion = getAppConfig().hideVersion;
+    customImageUrl = getAppConfig().loginImage;
+    imageUrl = '';
+    imageLocation = '';
+    imageCreator = '';
 
-    constructor(private authService: AuthService, private router: Router) {}
+    constructor(private authService: AuthService, private router: Router, private httpClient: HttpClient) {
+        if (this.customImageUrl) {
+            this.imageUrl = this.customImageUrl;
+        } else {
+            this.loadImage();
+        }
+    }
 
     logIn(): void {
         this.errorMessage = undefined;
@@ -35,6 +46,24 @@ export class LoginComponent {
         });
     }
 
+    loadImage() {
+        this.httpClient
+            .get('https://login-image.vendure.io')
+            .toPromise()
+            .then(res => {
+                this.updateImage(res);
+            });
+    }
+
+    updateImage(res) {
+        const user: any = (res as any).user;
+        const location: any = (res as any).location;
+
+        this.imageUrl = (res as any).urls.regular;
+        this.imageCreator = user.name;
+        this.imageLocation = location.name;
+    }
+
     /**
      * Attempts to read a redirect param from the current url and parse it into a
      * route from which the user was redirected after a 401 error.

+ 3 - 1
packages/admin-ui/src/lib/static/i18n-messages/de.json

@@ -286,7 +286,9 @@
     "username": "Benutzername",
     "view-next-month": "Nächsten Monat anzeigen",
     "view-previous-month": "Vorherigen Monat anzeigen",
-    "with-selected": "Auswahl..."
+    "with-selected": "Auswahl...",
+    "login-title": "Anmelden bei Vendure",
+    "login-image-title": "Hallo! Willkommen zurück. Schön, dich zu sehen."
   },
   "customer": {
     "add-customer-to-group": "Kunde zu Gruppe hinzufügen",

+ 3 - 1
packages/admin-ui/src/lib/static/i18n-messages/en.json

@@ -286,7 +286,9 @@
     "username": "Username",
     "view-next-month": "View next month",
     "view-previous-month": "View previous month",
-    "with-selected": "With {count} selected..."
+    "with-selected": "With {count} selected...",
+    "login-title": "Log in to Vendure",
+    "login-image-title": "Hi! Welcome back. Good to see you."
   },
   "customer": {
     "add-customer-to-group": "Add customer to group",

+ 1 - 0
packages/admin-ui/src/lib/static/styles/_variables.scss

@@ -4,3 +4,4 @@
 
 // breakpoints
 $breakpoint-small: 768px;
+$breakpoint-medium: 992px;

+ 7 - 0
packages/common/src/shared-types.ts

@@ -303,6 +303,13 @@ export interface AdminUiConfig {
      * @default false
      */
     hideVersion?: boolean;
+    /**
+     * @description
+     * The custom login image
+     *
+     * @since 1.9.0
+     */
+    loginImage?: string;
     /**
      * @description
      * Allows you to provide default reasons for a refund or cancellation. This will be used in the