From 379a193f60088ce4da420f3dec3fa2fa11fc7a55 Mon Sep 17 00:00:00 2001
From: Christoph Reiter <reiter.christoph@gmail.com>
Date: Tue, 23 Jun 2020 15:58:00 +0200
Subject: [PATCH] Split vpu-auth into vpu-auth and vpu-auth-button

vpu-auth listens to login/logout events which the button emits
and the button shows data from the auth-update event.
---
 packages/auth/src/auth-button.js   | 327 +++++++++++++++++++++++++++
 packages/auth/src/auth.js          | 349 ++++-------------------------
 packages/auth/src/index.js         |   3 +-
 packages/auth/src/util.js          |   7 +
 packages/auth/src/vpu-auth-demo.js |   6 +-
 packages/auth/src/vpu-auth.js      |   6 +-
 packages/auth/test/unit.js         |  18 ++
 7 files changed, 401 insertions(+), 315 deletions(-)
 create mode 100644 packages/auth/src/auth-button.js
 create mode 100644 packages/auth/src/util.js

diff --git a/packages/auth/src/auth-button.js b/packages/auth/src/auth-button.js
new file mode 100644
index 00000000..3b2608cb
--- /dev/null
+++ b/packages/auth/src/auth-button.js
@@ -0,0 +1,327 @@
+import {i18n} from './i18n.js';
+import {html, css} from 'lit-element';
+import {unsafeHTML} from 'lit-html/directives/unsafe-html.js';
+import {ScopedElementsMixin} from '@open-wc/scoped-elements';
+import * as commonStyles from 'vpu-common/styles';
+import {LitElement} from "lit-element";
+import {Icon, EventBus} from 'vpu-common';
+import {LoginStatus} from './util.js';
+
+export class AuthButton extends ScopedElementsMixin(LitElement) {
+
+    constructor() {
+        super();
+        this.lang = 'de';
+        this.showProfile = false;
+        this.showImage = false;
+        this._loginData = {};
+
+        this.closeDropdown = this.closeDropdown.bind(this);
+        this.onWindowResize = this.onWindowResize.bind(this);
+    }
+
+    static get scopedElements() {
+        return {
+            'vpu-icon': Icon,
+        };
+    }
+
+    static get properties() {
+        return {
+            lang: { type: String },
+            showProfile: { type: Boolean, attribute: 'show-profile' },
+            showImage: { type: Boolean, attribute: 'show-image' },
+            _loginData: { type: Object, attribute: false },
+        };
+    }
+
+    onWindowResize() {
+        this.updateDropdownWidth();
+    }
+
+    connectedCallback() {
+        super.connectedCallback();
+
+        this._bus = new EventBus();
+        this._bus.subscribe('auth-update', (data) => {
+            this._loginData = data;
+        });
+
+        window.addEventListener('resize', this.onWindowResize);
+        document.addEventListener('click', this.closeDropdown);
+    }
+
+    disconnectedCallback() {
+        window.removeEventListener('resize', this.onWindowResize);
+        this._bus.close();
+        document.removeEventListener('click', this.closeDropdown);
+        super.disconnectedCallback();
+    }
+
+    /**
+     * Set the dropdown width to almost the width of the web component
+     * We need to set the width manually because a percent width is in relation to the viewport
+     */
+    updateDropdownWidth() {
+        const dropdown = this.shadowRoot.querySelector("div.dropdown-menu");
+
+        if (!dropdown) {
+            return;
+        }
+
+        let viewportOffset = this.getBoundingClientRect();
+        let spaceToRIght = window.innerWidth - viewportOffset.left;
+        dropdown.setAttribute("style", `width: ${spaceToRIght - 20}px`);
+    }
+
+    onLoginClicked(e) {
+        this._bus.publish('auth-login');
+        e.preventDefault();
+    }
+
+    onLogoutClicked(e) {
+        this._bus.publish('auth-logout');
+    }
+
+    update(changedProperties) {
+        changedProperties.forEach((oldValue, propName) => {
+            if (propName === "lang") {
+                i18n.changeLanguage(this.lang);
+            }
+        });
+
+        super.update(changedProperties);
+    }
+
+    static get styles() {
+        // language=css
+        return css`
+            ${commonStyles.getThemeCSS()}
+
+            :host {
+                display: inline-block;
+            }
+
+            a {
+                color: currentColor;
+                cursor: pointer;
+                text-decoration: none;
+            }
+
+            img {
+                border-width: var(--vpu-border-width);
+                border-color: var(--vpu-dark);
+                border-style: solid;
+            }
+
+            .dropdown.is-active .dropdown-menu, .dropdown.is-hoverable:hover .dropdown-menu {
+                display: block;
+            }
+
+            .dropdown-menu {
+                display: none;
+                min-width: 5em;
+                max-width: 25em;
+                position: absolute;
+                z-index: 20;
+                border: solid 1px black;
+                border-radius: var(--vpu-border-radius);
+                overflow: hidden;
+                background-color: white;
+            }
+
+            .dropdown-content {
+                background-color: white;
+                padding-bottom: 0.5rem;
+                padding-top: 0.5rem;
+            }
+
+            .dropdown-content img {
+                max-width: 120px;
+            }
+
+            .menu a {
+                /*padding: 0.3em;*/
+                font-weight: 400;
+                color: #000;
+                display: block;
+                text-decoration: none;
+            }
+
+            .menu a:hover {
+                color: #E4154B;
+            }
+
+            .menu a.selected { color: white; background-color: black; }
+
+            .dropdown-item {
+                color: #4a4a4a;
+                display: block;
+                font-size: 0.875rem;
+                line-height: 1.5;
+                padding: 0.375rem 1rem;
+                position: relative;
+            }
+
+              .dropdown, img.login {
+                cursor: pointer;
+            }
+
+            a.dropdown-item {
+                width: initial !important;
+            }
+
+            .main-button {
+                min-width: 150px;
+            }
+
+            .menu-icon {
+                height: 1em;
+                width: 1em;
+                vertical-align: -0.1rem;
+            }
+
+            .login-box svg {
+                width: 1.1em;
+                height: 1.1em;
+                display: flex;
+            }
+
+            .login-button {
+                padding: 0.3em 0.4em;
+                transition: background-color 0.15s, color 0.15s;
+            }
+
+            .login-button:hover {
+                background-color: var(--vpu-dark);
+                color: var(--vpu-light);
+                cursor: pointer;
+                transition: none;
+            }
+
+            .login-box {
+                display: flex;
+                align-items: center;
+            }
+
+            .login-box:hover svg path {
+                fill: var(--vpu-light);
+            }
+
+            .login-box .label {
+                padding-left: 0.2em;
+            }
+
+            .dropdown-trigger {
+                display: flex;
+                align-items: center;
+            }
+
+            .dropdown-trigger .name {
+                white-space: nowrap;
+                overflow: hidden;
+                text-overflow: ellipsis;
+                min-width: 0;
+                margin-right: 0.5em
+            }
+        `;
+    }
+
+    setChevron(name) {
+        const chevron = this.shadowRoot.querySelector("#menu-chevron-icon");
+        if (chevron !== null) {
+            chevron.name = name;
+        }
+    }
+
+    onDropdownClick(event) {
+        event.stopPropagation();
+        event.currentTarget.classList.toggle('is-active');
+        this.setChevron(event.currentTarget.classList.contains('is-active') ? 'chevron-up' : 'chevron-down');
+        this.updateDropdownWidth();
+    }
+
+    closeDropdown() {
+        var dropdowns = this.shadowRoot.querySelectorAll('.dropdown');
+        dropdowns.forEach(function (el) {
+            el.classList.remove('is-active');
+        });
+        this.setChevron('chevron-down');
+    }
+
+    onProfileClicked(event) {
+        event.preventDefault();
+        const profileEvent = new CustomEvent("vpu-auth-profile", {
+            "detail": "Profile event",
+            bubbles: true,
+            composed: true,
+        });
+        this.dispatchEvent(profileEvent);
+    }
+
+    renderLoggedIn() {
+        const person = this._loginData.person;
+        const imageURL = (this.showImage && person && person.image) ? person.image : null;
+
+        return html`
+            <div class="dropdown" @click="${this.onDropdownClick}">
+                <a href="#">
+                    <div class="dropdown-trigger login-button">
+                        <div class="name">${this._loginData.name}</div>
+                        <vpu-icon class="menu-icon" name="chevron-down" id="menu-chevron-icon"></vpu-icon>
+                    </div>
+                </a>
+                <div class="dropdown-menu" id="dropdown-menu2" role="menu">
+                    <div class="dropdown-content" @blur="${this.closeDropdown}">
+                        ${imageURL ? html`<div class="dropdown-item"><img alt="" src="${imageURL}"></div>` : ''}
+                        <div class="menu">
+                            ${this.showProfile ? html`<a href="#" @click="${this.onProfileClicked}" class="dropdown-item">${i18n.t('profile')}</a>` :''}
+                            <a href="#" @click="${this.onLogoutClicked}" class="dropdown-item">${i18n.t('logout')}</a>
+                        </div>
+                    </div>
+                </div>
+            </div>
+        `;
+    }
+
+    renderLoggedOut() {
+        let loginSVG = `
+        <svg
+           viewBox="0 0 100 100"
+           y="0px"
+           x="0px"
+           id="icon"
+           role="img"
+           version="1.1">
+        <g
+           id="g6">
+            <path
+           style="stroke-width:1.33417916"
+           id="path2"
+           d="m 42.943908,38.894934 5.885859,6.967885 H 5.4215537 c -1.8393311,0 -3.4334181,1.741972 -3.4334181,4.064599 0,2.322628 1.4714649,4.064599 3.4334181,4.064599 H 48.829767 L 42.943908,60.9599 c -1.348843,1.596808 -1.348843,4.064599 0,5.661406 1.348843,1.596808 3.433418,1.596808 4.782261,0 L 61.705085,49.927418 47.726169,33.378693 c -1.348843,-1.596806 -3.433418,-1.596806 -4.782261,0 -1.348843,1.596807 -1.348843,4.064599 0,5.516241 z" />
+            <path
+           id="path4"
+           d="m 50,2.3007812 c -18.777325,0 -35.049449,10.9124408 -42.8261719,26.7246098 H 13.390625 C 20.672112,16.348362 34.336876,7.8007812 50,7.8007812 73.3,7.8007812 92.300781,26.7 92.300781,50 92.300781,73.3 73.3,92.300781 50,92.300781 c -15.673389,0 -29.345175,-8.60579 -36.623047,-21.326172 H 7.1640625 C 14.942553,86.8272 31.242598,97.800781 50.099609,97.800781 76.399609,97.800781 97.900391,76.4 97.900391,50 97.800391,23.7 76.3,2.3007812 50,2.3007812 Z" />
+        </g>
+        </svg>
+        `;
+
+        return html`
+            <a href="#" @click="${this.onLoginClicked}">
+                <div class="login-box login-button">
+                    <div class="icon">${unsafeHTML(loginSVG)}</div>
+                    <div class="label">${i18n.t('login')}</div>
+                </div>
+            </a>
+        `;
+    }
+
+    render() {
+        const loggedIn = (this._loginData.status === LoginStatus.LOGGED_IN);
+        return html`
+            <div class="authbox">
+                ${loggedIn ? this.renderLoggedIn() : this.renderLoggedOut()}
+            </div>
+        `;
+    }
+}
\ No newline at end of file
diff --git a/packages/auth/src/auth.js b/packages/auth/src/auth.js
index f33a6c7c..647efec0 100644
--- a/packages/auth/src/auth.js
+++ b/packages/auth/src/auth.js
@@ -1,23 +1,12 @@
 import {i18n} from './i18n.js';
-import {html, css} from 'lit-element';
-import {unsafeHTML} from 'lit-html/directives/unsafe-html.js';
-import {ScopedElementsMixin} from '@open-wc/scoped-elements';
 import JSONLD from 'vpu-common/jsonld';
 import * as commonUtils from 'vpu-common/utils';
-import * as commonStyles from 'vpu-common/styles';
-import {Icon, EventBus} from 'vpu-common';
-import VPULitElement from 'vpu-common/vpu-lit-element';
+import {EventBus} from 'vpu-common';
 import  {KeycloakWrapper} from './keycloak.js';
+import {LitElement} from "lit-element";
+import {LoginStatus} from './util.js';
 
 
-const LoginStatus = Object.freeze({
-    UNKNOWN: 'unknown',
-    LOGGING_IN: 'logging-in',
-    LOGGED_IN: 'logged-in',
-    LOGGING_OUT: 'logging-out',
-    LOGGED_OUT: 'logged-out',
-});
-
 /**
  * Keycloak auth web component
  * https://www.keycloak.org/docs/latest/securing_apps/index.html#_javascript_adapter
@@ -31,14 +20,12 @@ const LoginStatus = Object.freeze({
  *   window.VPUPerson: Person json object of the user (optional, enable by setting the `load-person` attribute,
  *                     which will dispatch a `vpu-auth-person-init` event when loaded)
  */
-export class Auth extends ScopedElementsMixin(VPULitElement) {
+export class Auth extends LitElement {
     constructor() {
         super();
         this.lang = 'de';
         this.forceLogin = false;
         this.loadPerson = false;
-        this.showProfile = false;
-        this.showImage = false;
         this.token = "";
         this.tokenParsed = null;
         this.subject = "";
@@ -53,17 +40,19 @@ export class Auth extends ScopedElementsMixin(VPULitElement) {
         // Create the events
         this.initEvent = new CustomEvent("vpu-auth-init", { "detail": "KeyCloak init event", bubbles: true, composed: true });
         this.personInitEvent = new CustomEvent("vpu-auth-person-init", { "detail": "KeyCloak person init event", bubbles: true, composed: true });
-        this.profileEvent = new CustomEvent("vpu-auth-profile", { "detail": "Profile event", bubbles: true, composed: true });
         this.keycloakDataUpdateEvent = new CustomEvent("vpu-auth-keycloak-data-update", { "detail": "KeyCloak data was updated", bubbles: true, composed: true });
 
-        this.closeDropdown = this.closeDropdown.bind(this);
         this._onKCChanged = this._onKCChanged.bind(this);
-   }
+    }
 
-    static get scopedElements() {
-        return {
-            'vpu-icon': Icon,
-        };
+    update(changedProperties) {
+        changedProperties.forEach((oldValue, propName) => {
+            if (propName === "lang") {
+                i18n.changeLanguage(this.lang);
+            }
+        });
+
+        super.update(changedProperties);
     }
 
     _onKCChanged(event) {
@@ -91,7 +80,7 @@ export class Auth extends ScopedElementsMixin(VPULitElement) {
             window.VPUPersonId = this.personId;
             window.VPUPerson = this.person;
 
-            this._setLoginStatus(LoginStatus.LOGGED_IN, tokenChanged);
+            this._setLoginStatus(LoginStatus.LOGGED_IN, tokenChanged || newPerson);
         } else {
             if (this._loginStatus === LoginStatus.LOGGED_IN) {
                 this._setLoginStatus(LoginStatus.LOGGING_OUT);
@@ -154,22 +143,19 @@ export class Auth extends ScopedElementsMixin(VPULitElement) {
         this._bus.publish('auth-update', {
             status: this._loginStatus,
             token: this.token,
+            name: this.name,
+            person: this.person,
         }, {
             retain: true,
         });
     }
 
-    /**
-     * See: https://lit-element.polymer-project.org/guide/properties#initialize
-     */
     static get properties() {
         return {
             lang: { type: String },
             forceLogin: { type: Boolean, attribute: 'force-login' },
             tryLogin: { type: Boolean, attribute: 'try-login' },
             loadPerson: { type: Boolean, attribute: 'load-person' },
-            showProfile: { type: Boolean, attribute: 'show-profile' },
-            showImage: { type: Boolean, attribute: 'show-image' },
             entryPointUrl: { type: String, attribute: 'entry-point-url' },
             keycloakConfig: { type: Object, attribute: 'keycloak-config' },
             name: { type: String, attribute: false },
@@ -211,6 +197,26 @@ export class Auth extends ScopedElementsMixin(VPULitElement) {
         this._kcwrapper = new KeycloakWrapper(baseURL, realm, clientId, silentCheckSsoRedirectUri);
         this._kcwrapper.addEventListener('changed', this._onKCChanged);
 
+
+
+        this._bus.subscribe('auth-login', () => {
+            this._kcwrapper.login({lang: this.lang, scope: this._getScope()});
+        });
+
+        this._bus.subscribe('auth-logout', () => {
+            // Keycloak will redirect right away without emitting events, so we have
+            // to do this manually here
+            if (this._loginStatus === LoginStatus.LOGGED_IN) {
+                this._setLoginStatus(LoginStatus.LOGGING_OUT);
+            }
+            this._kcwrapper.logout();
+            // In case logout was aborted, for example with beforeunload,
+            // revert back to being logged in
+            if (this._loginStatus === LoginStatus.LOGGING_OUT) {
+                this._setLoginStatus(LoginStatus.LOGGED_IN);
+            }
+        });
+
         const handleLogin = async () => {
             if (this.forceLogin || this._kcwrapper.isLoggingIn()) {
                 this._setLoginStatus(LoginStatus.LOGGING_IN);
@@ -226,291 +232,12 @@ export class Auth extends ScopedElementsMixin(VPULitElement) {
         };
 
         handleLogin();
-
-        this.updateComplete.then(() => {
-            window.onresize = () => {
-                this.updateDropdownWidth();
-            };
-        });
-
-        document.addEventListener('click', this.closeDropdown);
-    }
-
-    /**
-     * Set the dropdown width to almost the width of the web component
-     * We need to set the width manually because a percent width is in relation to the viewport
-     */
-    updateDropdownWidth() {
-        const dropdown = this._("div.dropdown-menu");
-
-        if (!dropdown) {
-            return;
-        }
-
-        let viewportOffset = this.getBoundingClientRect();
-        let spaceToRIght = window.innerWidth - viewportOffset.left;
-        dropdown.setAttribute("style", `width: ${spaceToRIght - 20}px`);
     }
 
     disconnectedCallback() {
-        this._bus.close();
         this._kcwrapper.removeEventListener('changed', this._onKCChanged);
-        document.removeEventListener('click', this.closeDropdown);
-        super.disconnectedCallback();
-    }
-
-    onLoginClicked(e) {
-        this._kcwrapper.login({lang: this.lang, scope: this._getScope()});
-        e.preventDefault();
-    }
-
-    onLogoutClicked(e) {
-        // Keycloak will redirect right away without emitting events, so we have
-        // to do this manually here
-        if (this._loginStatus === LoginStatus.LOGGED_IN) {
-            this._setLoginStatus(LoginStatus.LOGGING_OUT);
-        }
-        this._kcwrapper.logout();
-        // In case logout was aborted, for example with beforeunload,
-        // revert back to being logged in
-        if (this._loginStatus === LoginStatus.LOGGING_OUT) {
-            this._setLoginStatus(LoginStatus.LOGGED_IN);
-        }
-    }
-
-    update(changedProperties) {
-        changedProperties.forEach((oldValue, propName) => {
-            if (propName === "lang") {
-                i18n.changeLanguage(this.lang);
-            }
-        });
-
-        super.update(changedProperties);
-    }
-
-    static get styles() {
-        // language=css
-        return css`
-            ${commonStyles.getThemeCSS()}
-
-            :host {
-                display: inline-block;
-            }
-
-            a {
-                color: currentColor;
-                cursor: pointer;
-                text-decoration: none;
-            }
-
-            img {
-                border-width: var(--vpu-border-width);
-                border-color: var(--vpu-dark);
-                border-style: solid;
-            }
-
-            .dropdown.is-active .dropdown-menu, .dropdown.is-hoverable:hover .dropdown-menu {
-                display: block;
-            }
-
-            .dropdown-menu {
-                display: none;
-                min-width: 5em;
-                max-width: 25em;
-                position: absolute;
-                z-index: 20;
-                border: solid 1px black;
-                border-radius: var(--vpu-border-radius);
-                overflow: hidden;
-                background-color: white;
-            }
-
-            .dropdown-content {
-                background-color: white;
-                padding-bottom: 0.5rem;
-                padding-top: 0.5rem;
-            }
-
-            .dropdown-content img {
-                max-width: 120px;
-            }
-
-            .menu a {
-                /*padding: 0.3em;*/
-                font-weight: 400;
-                color: #000;
-                display: block;
-                text-decoration: none;
-            }
-
-            .menu a:hover {
-                color: #E4154B;
-            }
-
-            .menu a.selected { color: white; background-color: black; }
-
-            .dropdown-item {
-                color: #4a4a4a;
-                display: block;
-                font-size: 0.875rem;
-                line-height: 1.5;
-                padding: 0.375rem 1rem;
-                position: relative;
-            }
-
-              .dropdown, img.login {
-                cursor: pointer;
-            }
-
-            a.dropdown-item {
-                width: initial !important;
-            }
-
-            .main-button {
-                min-width: 150px;
-            }
-
-            .menu-icon {
-                height: 1em;
-                width: 1em;
-                vertical-align: -0.1rem;
-            }
-
-            .login-box svg {
-                width: 1.1em;
-                height: 1.1em;
-                display: flex;
-            }
-
-            .login-button {
-                padding: 0.3em 0.4em;
-                transition: background-color 0.15s, color 0.15s;
-            }
-
-            .login-button:hover {
-                background-color: var(--vpu-dark);
-                color: var(--vpu-light);
-                cursor: pointer;
-                transition: none;
-            }
-
-            .login-box {
-                display: flex;
-                align-items: center;
-            }
-
-            .login-box:hover svg path {
-                fill: var(--vpu-light);
-            }
-
-            .login-box .label {
-                padding-left: 0.2em;
-            }
-
-            .dropdown-trigger {
-                display: flex;
-                align-items: center;
-            }
-
-            .dropdown-trigger .name {
-                white-space: nowrap;
-                overflow: hidden;
-                text-overflow: ellipsis;
-                min-width: 0;
-                margin-right: 0.5em
-            }
-        `;
-    }
-
-    setChevron(name) {
-        const chevron = this.shadowRoot.querySelector("#menu-chevron-icon");
-        if (chevron !== null) {
-            chevron.name = name;
-        }
-    }
-
-    onDropdownClick(event) {
-        event.stopPropagation();
-        event.currentTarget.classList.toggle('is-active');
-        this.setChevron(event.currentTarget.classList.contains('is-active') ? 'chevron-up' : 'chevron-down');
-        this.updateDropdownWidth();
-    }
-
-    closeDropdown() {
-        var dropdowns = this.shadowRoot.querySelectorAll('.dropdown');
-        dropdowns.forEach(function (el) {
-            el.classList.remove('is-active');
-        });
-        this.setChevron('chevron-down');
-    }
-
-    onProfileClicked(event) {
-        event.preventDefault();
-        this.dispatchEvent(this.profileEvent);
-    }
-
-    renderLoggedIn() {
-        const imageURL = (this.showImage && this.person && this.person.image) ? this.person.image : null;
-
-        return html`
-            <div class="dropdown" @click="${this.onDropdownClick}">
-                <a href="#">
-                    <div class="dropdown-trigger login-button">
-                        <div class="name">${this.name}</div>
-                        <vpu-icon class="menu-icon" name="chevron-down" id="menu-chevron-icon"></vpu-icon>
-                    </div>
-                </a>
-                <div class="dropdown-menu" id="dropdown-menu2" role="menu">
-                    <div class="dropdown-content" @blur="${this.closeDropdown}">
-                        ${imageURL ? html`<div class="dropdown-item"><img alt="" src="${imageURL}"></div>` : ''}
-                        <div class="menu">
-                            ${this.showProfile ? html`<a href="#" @click="${this.onProfileClicked}" class="dropdown-item">${i18n.t('profile')}</a>` :''}
-                            <a href="#" @click="${this.onLogoutClicked}" class="dropdown-item">${i18n.t('logout')}</a>
-                        </div>
-                    </div>
-                </div>
-            </div>
-        `;
-    }
-
-    renderLoggedOut() {
-        let loginSVG = `
-        <svg
-           viewBox="0 0 100 100"
-           y="0px"
-           x="0px"
-           id="icon"
-           role="img"
-           version="1.1">
-        <g
-           id="g6">
-            <path
-           style="stroke-width:1.33417916"
-           id="path2"
-           d="m 42.943908,38.894934 5.885859,6.967885 H 5.4215537 c -1.8393311,0 -3.4334181,1.741972 -3.4334181,4.064599 0,2.322628 1.4714649,4.064599 3.4334181,4.064599 H 48.829767 L 42.943908,60.9599 c -1.348843,1.596808 -1.348843,4.064599 0,5.661406 1.348843,1.596808 3.433418,1.596808 4.782261,0 L 61.705085,49.927418 47.726169,33.378693 c -1.348843,-1.596806 -3.433418,-1.596806 -4.782261,0 -1.348843,1.596807 -1.348843,4.064599 0,5.516241 z" />
-            <path
-           id="path4"
-           d="m 50,2.3007812 c -18.777325,0 -35.049449,10.9124408 -42.8261719,26.7246098 H 13.390625 C 20.672112,16.348362 34.336876,7.8007812 50,7.8007812 73.3,7.8007812 92.300781,26.7 92.300781,50 92.300781,73.3 73.3,92.300781 50,92.300781 c -15.673389,0 -29.345175,-8.60579 -36.623047,-21.326172 H 7.1640625 C 14.942553,86.8272 31.242598,97.800781 50.099609,97.800781 76.399609,97.800781 97.900391,76.4 97.900391,50 97.800391,23.7 76.3,2.3007812 50,2.3007812 Z" />
-        </g>
-        </svg>
-        `;
-
-        return html`
-            <a href="#" @click="${this.onLoginClicked}">
-                <div class="login-box login-button">
-                    <div class="icon">${unsafeHTML(loginSVG)}</div>
-                    <div class="label">${i18n.t('login')}</div>
-                </div>
-            </a>
-        `;
-    }
+        this._bus.close();
 
-    render() {
-        const loggedIn = (this._loginStatus === LoginStatus.LOGGED_IN);
-        return html`
-            <div class="authbox">
-                ${loggedIn ? this.renderLoggedIn() : this.renderLoggedOut()}
-            </div>
-        `;
+        super.disconnectedCallback();
     }
 }
\ No newline at end of file
diff --git a/packages/auth/src/index.js b/packages/auth/src/index.js
index f8748296..9de95a6d 100644
--- a/packages/auth/src/index.js
+++ b/packages/auth/src/index.js
@@ -1,3 +1,4 @@
 import {Auth} from './auth.js';
+import {AuthButton} from './auth-button.js';
 
-export {Auth};
\ No newline at end of file
+export {Auth, AuthButton};
\ No newline at end of file
diff --git a/packages/auth/src/util.js b/packages/auth/src/util.js
new file mode 100644
index 00000000..2227557d
--- /dev/null
+++ b/packages/auth/src/util.js
@@ -0,0 +1,7 @@
+export const LoginStatus = Object.freeze({
+    UNKNOWN: 'unknown',
+    LOGGING_IN: 'logging-in',
+    LOGGED_IN: 'logged-in',
+    LOGGING_OUT: 'logging-out',
+    LOGGED_OUT: 'logged-out',
+});
diff --git a/packages/auth/src/vpu-auth-demo.js b/packages/auth/src/vpu-auth-demo.js
index 44898619..c9371ad7 100644
--- a/packages/auth/src/vpu-auth-demo.js
+++ b/packages/auth/src/vpu-auth-demo.js
@@ -2,6 +2,7 @@ import {i18n} from './i18n.js';
 import {html, LitElement} from 'lit-element';
 import {ScopedElementsMixin} from '@open-wc/scoped-elements';
 import {Auth} from './auth.js';
+import {AuthButton} from './auth-button.js';
 import * as commonUtils from 'vpu-common/utils';
 
 class AuthDemo extends ScopedElementsMixin(LitElement) {
@@ -13,6 +14,7 @@ class AuthDemo extends ScopedElementsMixin(LitElement) {
     static get scopedElements() {
         return {
           'vpu-auth': Auth,
+          'vpu-auth-button': AuthButton,
         };
     }
 
@@ -88,7 +90,9 @@ class AuthDemo extends ScopedElementsMixin(LitElement) {
                     <h1 class="title">Auth-Demo</h1>
                 </div>
                 <div class="container">
-                    <vpu-auth lang="${this.lang}" keycloak-config='{"url": "https://auth-dev.tugraz.at/auth", "realm": "tugraz", "clientId": "auth-dev-mw-frontend-local", "silentCheckSsoRedirectUri": "${silentCheckSsoUri}", "scope": "optional-test-scope"}' load-person try-login show-image></vpu-auth>
+                    <vpu-auth lang="${this.lang}" keycloak-config='{"url": "https://auth-dev.tugraz.at/auth", "realm": "tugraz", "clientId": "auth-dev-mw-frontend-local", "silentCheckSsoRedirectUri": "${silentCheckSsoUri}", "scope": "optional-test-scope"}' load-person try-login></vpu-auth>
+
+                    <vpu-auth-button lang="${this.lang}" show-image></vpu-auth-button>
                 </div>
             </section>
 
diff --git a/packages/auth/src/vpu-auth.js b/packages/auth/src/vpu-auth.js
index c6be6253..50461dcb 100644
--- a/packages/auth/src/vpu-auth.js
+++ b/packages/auth/src/vpu-auth.js
@@ -1,4 +1,6 @@
-import * as commonUtils from 'vpu-common/utils';
+import {defineCustomElement} from 'vpu-common/utils';
 import {Auth} from './auth.js';
+import {AuthButton} from './auth-button.js';
 
-commonUtils.defineCustomElement('vpu-auth', Auth);
+defineCustomElement('vpu-auth', Auth);
+defineCustomElement('vpu-auth-button', AuthButton);
diff --git a/packages/auth/test/unit.js b/packages/auth/test/unit.js
index 3a3c63e7..4f913340 100644
--- a/packages/auth/test/unit.js
+++ b/packages/auth/test/unit.js
@@ -26,6 +26,24 @@ suite('vpu-auth basics', () => {
   });
 });
 
+suite('vpu-auth-button', () => {
+    let node;
+  
+    suiteSetup(async () => {
+      node = document.createElement('vpu-auth-button');
+      document.body.appendChild(node);
+      await node.updateComplete;
+    });
+  
+    suiteTeardown(() => {
+      node.remove();
+    });
+  
+    test('should render', () => {
+        expect(node).to.have.property('shadowRoot');
+    });
+  });
+
 suite('vpu-auth-demo basics', () => {
   let node;
 
-- 
GitLab