简体   繁体   中英

Login page for a SPA in Polymer

I'm fairly new at Polymer and have used the Polymer 2.0 Starter Kit template for all of my apps. The majority of my apps are very similar - a login page that redirects you to different portals based on a user role (usually admin or user). The portals have an app-drawer with different views setup via iron-pages .

My current flow duplicates the my-app template within each 'portal'. I'm just uncertain if this is the correct way of doing this. I'll explain further.

Using my-app as is from the starter kit with iron-pages to load the login-view , admin-portal , and user-portal, defaulting to login-view .

After a user has logged in, the application will load admin-portal or user-portal depending on which user has logged in.

Both admin-portal and user-portal have a structure very similar to my-app, with an app-drawer and then iron-pages loading views specific to admin capabilities or user capabilities. I was mainly wondering if this is best practice - as you always have a nested iron-pages . Is there away to abstract the login-view ?

Code:

my-app - out of the box template

<app-location
    route="{{route}}"
    url-space-regex="^[[rootPath]]">
</app-location>

<app-route
    route="{{route}}"
    pattern="[[rootPath]]:page"
    data="{{routeData}}"
    tail="{{subroute}}">
</app-route>

<iron-pages
    selected="[[page]]"
    attr-for-selected="name"
    fallback-selection="view404"
    role="main">
  <login-view name="login" current-user="{{currentUser}}"></login-view>
  <superuser-portal name="superuser"></superuser-portal>
  <admin-portal name="admin" current-user="{{currentUser}}"></admin-portal>
  <doctor-portal name="doctor" current-user="{{currentUser}}"></doctor-portal>
  <my-view404 name="view404"></my-view404>
</iron-pages>

  _routePageChanged(page) {
    //console.log(this.user.email);
    // If no page was found in the route data, page will be an empty string.
    // Default to 'view1' in that case.
    this.page = page || 'login';
  }

  _pageChanged(page) {
    console.log('_pageChanged: ' + page);

    let resolvedPageUrl = this.resolveUrl('my-' + page + '.html');

    if(page === 'login') {
      resolvedPageUrl = this.resolveUrl('login-view.html');
    } else if(page === 'superuser') {
      resolvedPageUrl = this.resolveUrl('superuser-portal.html');
    } else if(page === 'admin') {
      resolvedPageUrl = this.resolveUrl('admin-portal.html');
    } else if(page === 'doctor') {
      resolvedPageUrl = this.resolveUrl('doctor-portal.html');
    }

    console.log('resolvedPageUrl: ' + resolvedPageUrl);

    Polymer.importHref(
        resolvedPageUrl,
        null,
        this._showPage404.bind(this),
        true);
  }

admin-portal - again, basically same as out of box template

    <app-route route="{{route}}" pattern="/admin/:page" data="{{routeData}}" tail="{{subroute}}">
    </app-route>

    <!-- <firebase-auth id="auth" user="{{user}}" on-error="handleError"></firebase-auth> -->

    <app-drawer-layout fullbleed narrow="{{narrow}}">
      <app-drawer id="drawer" slot="drawer" swipe-open="[[narrow]]">
        <div class="drawerHeader">
          <div class="drawerTitle">GetonHealth</div>
          <div class="drawerSubtitle">[[displayName]]</div>
        </div>
        <div style="margin-top: 24px; color: white;">
          <vaadin-list-box>
            <iron-selector attr-for-selected="name" selected="{{routeData.page}}">
              <vaadin-item name="calendar">
                <iron-icon icon="vaadin:calendar"></iron-icon>
                Calendar
              </vaadin-item>
              <vaadin-item name="doctors">
                <iron-icon icon="vaadin:specialist"></iron-icon>
                Manage Doctors
              </vaadin-item>
              <vaadin-item name="view3">
                <iron-icon icon="vaadin:cog-o"></iron-icon>
                Settings
              </vaadin-item>
              <vaadin-item name="view4">
                <iron-icon icon="vaadin:info-circle-o"></iron-icon>
                Help
              </vaadin-item>
              <vaadin-item name="logout">
                <iron-icon icon="vaadin:exit-o"></iron-icon>
                Sign Out
              </vaadin-item>
            </iron-selector>
          </vaadin-list-box>
        </div>
      </app-drawer>
      <app-header-layout>
        <app-header>
          <app-toolbar effects="waterfall">
            <paper-icon-button icon="my-icons:menu" drawer-toggle=""></paper-icon-button>
            <div class="main-title" main-title>[[pageTitle]]</div>
          </app-toolbar>
        </app-header>

        <iron-pages selected="[[page]]" attr-for-selected="name" fallback-selection="view404" role="main">
          <admin-dashboard name="dashboard"></admin-dashboard>
          <admin-manage-doctors name="doctors"></admin-manage-doctors>
          <manage-doctor-detail name="doctor" page-title="{{pageTitle}}"></manage-doctor-detail>
          <admin-calendar-view name="calendar" narrow="[[narrow]]"></admin-calendar-view>
          <admin-new-event name="newevent"></admin-new-event>
          <admin-event-detail name="event" page-title="{{pageTitle}}"></admin-event-detail>
          <admin-reschedule-view name="reschedule" page-title="{{pageTitle}}"></admin-reschedule-view>
          <admin-new-appointment name="newappointment" current-user="{{currentUser}}"></admin-new-appointment>
          <my-view404 name="view404"></my-view404>
        </iron-pages>

      </app-header-layout>
    </app-drawer-layout>

  </template>

  <script>
    class AdminPortal extends Polymer.Element {
      static get is() { return 'admin-portal'; }

      static get properties() {
        return {
          pageTitle: String,
          displayName: String,
          currentUser: {
            type: Object,
            value: {}
          },
          page: {
            type: String,
            reflectToAttribute: true,
            observer: '_pageChanged',
          },
          routeData: Object,
          subroute: Object,
        };
      }

      static get observers() {
        return [
          '_routePageChanged(routeData.page)',
        ];
      }

      _routePageChanged(page) {
        // If no page was found in the route data, page will be an empty string.
        // Default to 'view1' in that case.
        this.page = page || 'calendar';
      }

      _pageChanged(page) {
        console.log('_pageChanged: ' + page);

        let resolvedPageUrl = this.resolveUrl('my-' + page + '.html');

        if (page === 'dashboard') {
          this.set('pageTitle', 'Dashboard');
          resolvedPageUrl = this.resolveUrl('admin/views/admin-dashboard.html');
        } else if (page === 'doctors') {
          this.set('pageTitle', 'Manage doctors');
          resolvedPageUrl = this.resolveUrl('admin/views/admin-manage-doctors.html');
        } else if (page === 'doctor') {
          this.set('pageTitle', '');
          resolvedPageUrl = this.resolveUrl('admin/views/manage-doctor-detail.html');
        } else if (page === 'calendar') {
          this.set('pageTitle', 'Calendar');
          this.set('queryParams', {});
          resolvedPageUrl = this.resolveUrl('admin/views/admin-calendar-view.html');
        } else if (page === 'newevent') {
          this.set('pageTitle', 'Create new event');
          resolvedPageUrl = this.resolveUrl('admin/views/admin-new-event.html');
        } else if (page === 'event') {
          resolvedPageUrl = this.resolveUrl('admin/views/admin-event-detail-test.html');
        } else if (page === 'newappointment') {
          resolvedPageUrl = this.resolveUrl('admin/views/admin-new-appointment.html');
        } else if (page === 'reschedule') {
          resolvedPageUrl = this.resolveUrl('admin/views/admin-reschedule-view.html');
        } else if (page === 'logout') {
          this.logout();
          return;
        }

        Polymer.importHref(
          resolvedPageUrl,
          null,
          this._showPage404.bind(this),
          true);

        if (!this.$.drawer.persistent) {
          this.$.drawer.close();
        }
      }

login-view

performLogin() {
    this.$.auth.signInWithEmailAndPassword(this.username, this.password)
      .then(response => {
        this.username = '';
        this.password = '';
        this.getUserFromFirestore(response);
      }).catch(error => {
        console.error(error);
        this.$.inputPassword.invalid = true;
        this.$.inputPassword.errorMessage = error.message;
        this.$.inputPassword.validate();
      });
  }

  getUserFromFirestore(user) {
    firestore.collection('users').doc(user.uid)
      .get()
      .then(doc => {
        console.log(doc.data());
        //this.set('currentUser', this.docToObject(doc));
        this.changePage(user.uid, doc.data().role);
      })
      .catch(error => {
        console.error(error);
      });
  }

  changePage(uid, role) {

    let page = '';

    if (role === 'superuser') {
      page = '/superuser/dashboard';
    } else if (role === 'admin') {
      page = '/admin/calendar';
    } else if (role === 'doctor') {
      page = '/doctor/clinics';
    }

    setTimeout(() => {
      window.history.pushState({}, null, page);
      window.dispatchEvent(new CustomEvent('location-changed'));
    }, 1000);
  }

Nothing seems particularly wrong with this implementation and it seems straightforward. The login having its own view is also fine. There is displayable DOM associated with it ("please log in", username, password box), so there is nothing wrong with having it as a selectable view with authentication knowledge within it.

Just make sure that the login page is not the gatekeeper to meaningful action. ie The user cannot add / delete data from firestore simply by changing pages in the inspector. iron-pages !== security, only navigation.

Additionally, it is not against best practices to have nested iron-pages . iron-pages is essentially a glorified element.setAttribute('hidden', '') and element.removeAttribute('hidden') . Though, a common gotcha with nested iron-pages is to keep track of exit state when navigating away in the parent iron-pages . eg login goes to admin then in admin you navigate to reschedule, then you log in as a different admin, the admin portal probably shouldn't automatically be at reschedule.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM