Is there any way to listen User login event using Theme component

Hi All,

I want to display the Popup box using the Theme component when the User login first time.

1. I have tried to override the login action of the login controller using Theme component to display the Popup box while User successfully login.
Here in the login action, I have added bootbox.alert(“Test Alert”); code before hiddenLoginForm.submit();.

But Popup is displayed until the hidderLoginForm is submitted and then redirected to the Home page.

https://github.com/discourse/discourse/blob/7af061fafa67bd332b4063a70ec34703df51431c/app/assets/javascripts/discourse/app/controllers/login.js#L226

My requirement is Popup box should be displayed after redirecting to the Home page after successful login.

2. To check whether the User login for the first time I tried to check the value of the previous_visit_at property of the current user.
If previous_visit_at = null (ie. The User is login for the first time.)
But here also my test fails, as I login a second time the value of the previous_visit_at property was null.
I want to know when exactly the value of previous_visit_at property is updated.

Please help me to achieve the above requirements

Thank you,
Saurabh Khandelwal

3 Likes

Hey @Saurabh_Khandelwal :wave:

I would advise against doing it this way; login is one of the most critical actions, and any overrides to that action will be fragile and prone to breaking if we make any changes in the area in core.

If you recall, when you first signed up on this site, you saw something like this.

The conditions we use to check for this are

!user.read_first_notification && !user.enforcedSecondFactor

So, if you use the same conditions in an initializer, you’ll be able to render a Bootbox on the user’s very first-page view after login.

You can try something like this in your theme component.

import { withPluginApi } from "discourse/lib/plugin-api";
import bootbox from "bootbox";

export default {
  name: "first-login-bootbox",
  initialize() {
    withPluginApi("0.8", api => {
      const user = api.getCurrentUser();
      if (!user) return;

      if (!user.read_first_notification && !user.enforcedSecondFactor) {
        const text = `Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor`;
        bootbox.alert(text);
      }
    });
  }
};

and that will give you something like this

You can use HTML in the text of the bootbox if needed. Once the user dismisses the bootbox and clicks on the circled avatar, they’ll never see the bootbox again.

Please note that bootboxes are meant for simple dialogs/confirmations. If you need something a little bit more complicated than that, then you’re probably better of using a modal with showModal()

12 Likes

Thanks, @Johani for your advice. I will avoid making changes in the login action and try to implement as you mention.

.

2 Likes

Hi, @Johani I have added a simple hyperlink (say “/new-topic”) within the popup box which opens in a new tab.
When I click on that link it opens in a new tab but again it displays a Popup box as we have checked for the below conditions

!user.read_first_notification && !user.enforcedSecondFactor 

In my case, the Popup box should not be displayed after visiting the link

Can we update values for these properties after the Popup box is displayed once, if yes then how can we do this?

1 Like

I’m afraid that’s not possible.

Those properties don’t have setters; even if they did, your changes would only temporarily apply in the first window. Once the user visits the second tab, the data will be based on what’s stored in the database. Themes don’t have access to the backend; they can only change the frontend.

What you can do is add a hash to your link and check for that like this.

import { withPluginApi } from "discourse/lib/plugin-api";
import bootbox from "bootbox";

export default {
  name: "first-login-bootbox",
  initialize() {
    withPluginApi("0.8", api => {
      const user = api.getCurrentUser();
      if (!user) return;

      if (
        !user.read_first_notification &&
        !user.enforcedSecondFactor &&
        !window.location.hash
      ) {
        const text = `Lorem ipsum dolor sit amet <a href="http://localhost:3000/new-topic#some-hash" target="_blank">Link</a>, consectetur adipiscing elit, sed do eiusmod tempor`;
        bootbox.alert(text);
      }
    });
  }
};

I’m not sure if linking to “/new-topic” in your post was just an example or if it’s what you want to do. If it’s the desired result, then you have another problem. Even if the bootbox is not displayed on the page with the hash, they’ll still see this…

…and the composer will not open, which makes sense since it’s very unexpected for a user to start typing a topic on their very first page-view immediately.

Might I ask what you’re trying to accomplish here? Are you trying to inform the user of something or the other?

The way I’ve seen this done on other sites is to edit the welcome message, but if that’s an option, there are alternatives.

Here’s what I suggest

  1. create a topic and add all of the information you want there
  2. publish that topic
  3. link to that topic in the bootbox and open that link in a new tab.

This way, when the user clicks on the link, they’ll see something like this (without the overlay)

Once they’re done with that page, they can go back to the first tab, dismiss the bootbox, read the first notification and then continue using the site.

This way, you don’t even need to add/check for a hash. Here’s an example snippet

import { withPluginApi } from "discourse/lib/plugin-api";
import bootbox from "bootbox";

export default {
  name: "first-login-bootbox",
  initialize() {
    withPluginApi("0.8", api => {
      const user = api.getCurrentUser();
      if (!user) return;

      if (!user.read_first_notification && !user.enforcedSecondFactor) {
        const text = `Lorem ipsum dolor sit amet <a href="http://my.site.com/pub/bentley-flying-spur-s-production-milestone" target="_blank">Link</a>, consectetur adipiscing elit, sed do eiusmod tempor`;
        bootbox.alert(text);
      }
    });
  }
};
2 Likes

I’m trying to have some links like “/new-topic” “/my/preferences” within Popup.

Otherwise, I was thinking to display Popup only for the home page by checking the current URL.
So while visiting other pages Popup will not will be displayed.

1 Like

Even if I check for home page URL after clicking on “/new-topic” link, above issue will occur

Hi Saurabh,
Yes, I think checking the current url and showing the popup only on the home page should work…

We can show the new user introduction from Discobot after some time e.g. 2 mins:

1 Like

Hi, @Johani

Can we create a widget as shown below and call bootbox() within it.
And after calling bootbox can we call “skipNewUserTips()” method. Which is also used by Discourse in the header-notifications widget.

<script type="text/discourse-plugin" version="0.8">
 const { h } = require('virtual-dom');
 const { ajax } = require("discourse/lib/ajax").default;
 const DiscourseURL =  require("discourse/lib/url");
 const { userPath } = require("discourse/lib/url");

api.createWidget("welcome-popup", {
  tagName: "div.welcome-popup",
  html(attrs) {
      let user = this.currentUser;
      if (!user) return;
      if (
        !user.get("read_first_notification") &&
        !user.get("enforcedSecondFactor")
      ) {
          const title = `<div class="first-login-bootbox-title">You're a member. Welcome aboard!</div>`;
          const body = `<div class="first-login-bootbox-body">Now you can: </br> <ol class="user-suggestions"><li><a href="/new-topic" target="_blank" >Ask a question or start a discussion</a></li> <li><a href="/my/preferences/tags" target="_blank">Set up your notification settings</a></li></ol></div>`;
          bootbox.alert({
                        title: title,
                        message: body
                    });
            this.skipNewUserTips();        
      }
      return null;
  },

  skipNewUserTips() {

    ajax(userPath(this.currentUser.username_lower), {
      type: "PUT",
      data: {
        skip_new_user_tips: true,
      },
    }).then(() => {
      this.currentUser.set("skip_new_user_tips", true);
    });
  },
});
</script>

<script
  type="text/x-handlebars"
  data-template-name="/connectors/below-site-header/welcome-popup"
>
  {{mount-widget widget="welcome-popup"}}
</script> 

Discourse code for the header-notifications widget:
https://github.com/discourse/discourse/blob/master/app/assets/javascripts/discourse/app/widgets/header.js#L101

Will it create any issue?

1 Like

This skips this overlay altogether for all new users. So they’ll never see it.

If that’s OK with you then, yes, it should work.

You do need to change the bootbox call. Otherwise, you’ll get this.

instead of doing this

You should only pass a single argument.

Those options were added to newer versions of Bootbox and won’t work with the version that we’re currently using in Discourse (we’re discussing this internally, but for now, you can’t use those)

Also, since you’re creating a PUT request, then you can also skip this.

this.currentUser.set("skip_new_user_tips", true);

So, maybe something like this.

  api.createWidget("welcome-popup", {
    tagName: "div.welcome-popup",
    html(attrs) {
      let user = this.currentUser;
      if (!user) return;
      if (
        !user.get("read_first_notification") &&
        !user.get("enforcedSecondFactor")
      ) {
        const body = `<h2 class="first-login-bootbox-title">You're a member. Welcome aboard!</h2>
                      <hr>
                      <div class="first-login-bootbox-body">
                        Now you can:
                        <br>
                        <ol class="user-suggestions">
                          <li>
                            <a href="/new-topic" target="_blank"
                              >Ask a question or start a discussion</a
                            >
                          </li>
                          <li>
                            <a href="/my/preferences/tags" target="_blank"
                              >Set up your notification settings</a
                            >
                          </li>
                        </ol>
                      </div>`;
        bootbox.alert(body);
        this.skipNewUserTips();
      }
      return null;
    },

    skipNewUserTips() {
      ajax(userPath(this.currentUser.username_lower), {
        type: "PUT",
        data: {
          skip_new_user_tips: true
        }
      });
    }
  });
2 Likes

Hi @Johani,

As we set skip_new_user_tips to true, the Discobot Greeting notification is also skipped. It is ok if the first notification overlay mask is not displayed but we want the Discobot Greeting notification.

Discobot notification:
Without_DiscobotNotification

For this, we had changed the code so that the Discobot Greeting notification will be loaded for all new users.
Now we added a new method to the header widget “closeFirstNotificationMask()” where we set “ringBackdrop” to false and “userVisible” to its inverse. And called this method after calling bootbox method in the welcome-popup widget.

We took reference from the “toggeleUserMenu” method of header widget which is called when the User icon is clicked.
https://github.com/discourse/discourse/blob/d2a04621862aa7f7fc283112d542648e9f3fcab8/app/assets/javascripts/discourse/app/widgets/header.js#L483

<script src="https://cdnjs.cloudflare.com/ajax/libs/bootbox.js/5.4.1/bootbox.min.js" integrity="sha512-eoo3vw71DUo5NRvDXP/26LFXjSFE1n5GQ+jZJhHz+oOTR4Bwt7QBCjsgGvuVMQUMMMqeEvKrQrNEI4xQMXp3uA==" crossorigin="anonymous"></script>

<script type="text/discourse-plugin" version="0.8">
 const { h } = require('virtual-dom');
 const { ajax } = require("discourse/lib/ajax").default;

/* Adding new Method to header Widget which will call after displaying Welcome-popup*/
api.reopenWidget("header",{
    closeFirstNotificationMask() {
        this.state.ringBackdrop = false;
        this.state.userVisible = !this.state.userVisible;
        this.toggleBodyScrolling(this.state.userVisible);
    }
});

/* Created new widget "welcome-popup", this widget will be render only when user login for the first time */
api.createWidget("welcome-popup", {
  tagName: "div.welcome-popup",
  html(attrs) {
      let user = this.currentUser;
      if (!user) return;
      if ( !user.get("read_first_notification") && !user.get("enforcedSecondFactor") ) {
          const title = `<div class="first-login-bootbox-title">You're a member. Welcome aboard!</div>`;
          const body = `<div class="first-login-bootbox-body">Now you can: </br> <ol class="user-suggestions"><li><a href="/new-topic" target="_blank" >Ask a question or start a discussion</a></li> <li><a href="/my/preferences/tags" target="_blank">Set up your notification settings</a></li></ol></div>`;
          
          bootbox.alert({
                        title: title,
                        message: body
                    });
            //Calling closeFirstNotificationMask action method of header widget
            this.sendWidgetAction("closeFirstNotificationMask", this.attrs);
        }
      return null;
  },
  
});

/* Below code will render "welcome-popup" widget after rendering "header-notifications" widget when User login first time*/
api.decorateWidget("header-notifications:after", helper => { 
    if(!helper.attrs.active && helper.attrs.ringBackdrop){
        return helper.attach("welcome-popup", helper.attrs);     
    }else{
        return null;    
    }
});
</script>

One more thing, we using Bootbox 5.4.1 version by including the below script in our theme component as currently Discourse not support the multi-parameter bootbox method. Is it Ok?

<script src="https://cdnjs.cloudflare.com/ajax/libs/bootbox.js/5.4.1/bootbox.min.js" integrity="sha512-eoo3vw71DUo5NRvDXP/26LFXjSFE1n5GQ+jZJhHz+oOTR4Bwt7QBCjsgGvuVMQUMMMqeEvKrQrNEI4xQMXp3uA==" crossorigin="anonymous"></script>

Please give us your opinion, thank you!

2 Likes

This looks good :+1:

I tried it locally and didn’t see any problems. I made a few minor changes.

<script
  src="https://cdnjs.cloudflare.com/ajax/libs/bootbox.js/5.4.1/bootbox.min.js"
  integrity="sha512-eoo3vw71DUo5NRvDXP/26LFXjSFE1n5GQ+jZJhHz+oOTR4Bwt7QBCjsgGvuVMQUMMMqeEvKrQrNEI4xQMXp3uA=="
  crossorigin="anonymous"
></script>

<script type="text/discourse-plugin" version="0.8">
  const user = api.getCurrentUser();
  if (!user || user.read_first_notification || user.enforcedSecondFactor) {
    return;
  }

  const bootboxTitle = `<div class="first-login-bootbox-title">You're a member. Welcome aboard!</div>`;
  const bootboxBody = `<div class="first-login-bootbox-body">Now you can: </br> <ol class="user-suggestions"><li><a href="/new-topic" target="_blank" >Ask a question or start a discussion</a></li> <li><a href="/my/preferences/tags" target="_blank">Set up your notification settings</a></li></ol></div>`;

  /* Adding new Method to header Widget which will call after displaying Welcome-popup*/
  api.reopenWidget("header", {
    closeFirstNotificationMask() {
      this.state.ringBackdrop = false;
      this.state.userVisible = !this.state.userVisible;
    }
  });

  /* Created new widget "welcome-popup", this widget will be render only when user login for the first time */
  api.createWidget("welcome-popup", {
    tagName: "div.welcome-popup",
    html(attrs) {
      //Calling closeFirstNotificationMask action method of header widget
      this.sendWidgetAction("closeFirstNotificationMask", attrs);

      bootbox.alert({
        title: bootboxTitle,
        message: bootboxBody
      });
    }
  });

  /* Below code will render "welcome-popup" widget after rendering "header-notifications" widget when User login first time*/
  api.decorateWidget("header-notifications:before", helper => {
    if (!helper.attrs.active && helper.attrs.ringBackdrop) {
      return helper.attach("welcome-popup", helper.attrs);
    }
  });
</script>

Yes, that’s OK. Discourse loads the bootbox version we use as a shim, so loading a different version in your theme component won’t change anything in core. The new version will only be used in your theme component. The only downside is that it adds one extra request and ~4kb to the initial page load.

3 Likes

Hi @Johani as you loaded the welcome-popup widget before the header-notifications widget it seems that Discobot notification is not loaded and also after clicking on the Links within welcome-popup again welcome-popup is display to the user.
I have tested it by loading the welcome-popup widget after the header-notifications widget that works fine, So can I change it to “header-notifications:after”

That’s strange :thinking:

I just tried the same code again with 3 different new users, and I get the expected result everytime. I see the popup on the first page view, and the Discobot greeting message is sent. Subsequent page views don’t have the popup.

Sure, if it works for you, then the decorator location shouldn’t matter.

3 Likes

Thanks @Johani, We will use after decorator.

1 Like