Assign Badges sending a notification on every page visit; help!

Hi all,

I am using the Badges API in the Wix Forum app to automate when users are assigned a badge based on certain criteria.

Right now, the users are receiving the correct badge for the criteria (likes/views on a post) however, the code runs every single time any user visits that page, and so the users are receiving hundreds of notifications.

I’d like to have the users automatically receive their badge, receive one single notification that they have received their badge, and then never be notified again.

I have attached a screen shot of my code below, and a notifications page showing the issue.

Please go easy on me, I’m a total beginner and self-taught from just a couple of weeks ago.

Hey Joshua, responding to your comment on my Badges tutorial. I don’t see the code that sends the notifications, but I may be misreading it. Can you point me to exactly what function is adding notifications?

I think the code that adds the notifications is part of the Forum app itself. I also have no idea where those notifications are coming from! I’d love to just turn them off completely to solve this, if needed.

Ah…I see. I haven’t worked with the forum app myself. But I will see if I can find any documentation around that may help you figure it out

@amandam Thank you so much! It would be a huge help… I’ve been at it for 10+ hours straight and just can’t seem to solve this.

The only thing I can think of is that maybe there is a way to specify that in my topPostOwners array and my viewsPostOwners array that I ONLY want that to trigger the current member. Maybe something to do with a getCurrentMember?

That way I could trigger all of this code on a specific page that users could go visit to claim their badge.

Very new to all of this so I really appreciate your help.

I’m looking at your screenshot again and if users are getting notified that they have received a badge they already have (hence causing duplicate notifications) what you need to do is check the user current badges first and only run the assign function if they do not have the badge.

If no new action is taking place, there should be no notification. So check and see if your assign badges code is running when it should not is my guess.

As far as turning off notifications all together, I only see a way to do this as a user, not a site owner. But customer support may know better on that one if you want to reach out through the bot

Makes perfect sense.

Could you help me the code for checking the current badges first? I thought of that sort of thing, but I haven’t found that anywhere in the API reference.

(thanks in advance! You’re helping the non-profit classical music community right now!)

Sure! Happy to help and fun fact for you - I used to sing classical music and opera many many years ago. Clearly I wasn’t all that good, so here I am :slight_smile:

This is covered in my Badges API video. Getting the user badges is a function you must put in the backend. It can take a parameter of user id which you can get by calling currentMember on the page code.

Take a look the video again here to see how I call the backend function and then what I do with the response in the page code to be able to use the results.

And the code snippets I used are here:

Ok great! I’ll give this a shot and report back.

Is this thread the best place to reach you?

The great thing about singing is that you can do it anywhere, with anyone! I’m a full-time percussionist, but becoming more and more of a classical music entrepreneur. Thanks for the help along the way!

@percussionconservato Oh I still sing all the time just no longer attempting it professionally as I was when I was 17 and trying to avoid college!

And yes, you may respond here if you get stuck along the way. If you post anymore code, please copy it into a code block using the </> icon. It is easier to read that way.

@amandam

I got it! I’m sure it’s a bit clunky, but working just fine! Thanks so much Amanda!

I’ll reach out again if you don’t mind… I am trying to build some online courses as well that look like the Wix Learn course pages.

Those are simple and elegant and I’d love to recreate them! Do you know who was in charge of designing that project? Maybe you?? Would be great to chat with them briefly to see which API’s they used to achieve all the various functionality.

@percussionconservato Wonderful! So glad it’s working for you.

re: WixLearn site, I am not sure but will look into it. It may take some time for me to get back to you but I will follow up

@amandam thanks a million! Your Tutorial was really great too. Super easy to follow. Well done!

@amandam sadly… as it turns out. My code only partially works.

It’s giving a member a badge when they click on a button, but it’s not adhering to the criteria/query the badge is supposed to have. Here’s my front end:

import { currentMember } from ‘wix-members’ ;
import { getMemberBadges , getTenuredBadge , getPatronBadge } from ‘backend/getBadges’
import wixData from ‘wix-data’

const options = {
fieldsets : [ ‘FULL’ ]
}
$w . onReady ( async function () {

$w ( '#myBadges' ). data  = [] 

**const**  loggedInMember  =  **await**  currentMember . getMember ( options ); 

**const**  memberBadges  =  **await**  getMemberBadges ( loggedInMember . _id ); 

**if**  ( memberBadges ) { 
    myBadges ( memberBadges ) 
} 

$w ( '#claimBadges' ). onClick ( **async**  () => { 

    **const**  TenuredBadge  =  **await** 
    wixData . query ( "Members/Badges" ) 
        . contains ( "title" ,  "Tenured" ) 
        . find (); 
    **const**  TenuredBadgeId  =  TenuredBadge . items [ 0 ]. _id ; 
    wixData . query ( "Forum/Posts" ) 
        . gt ( "likeCount" ,  0 ) 
        . find () 
        . then (( results ) => { 
            **if**  ( results . items . length  >  0 ) { 
                **const**  topPosts  =  results . items ; 
                **const**  member  =  topPosts . map ( post  =>  post . _memberId ); 
                getTenuredBadge ( TenuredBadgeId , [ member ]); 
            }  **else**  { 
                console . log ( "No top posts" ); 
            } 
        }) 
        . **catch** (( error ) => { 
            console . log ( error ); 
            "console.error();" 
        }); 

    **if**  ( TenuredBadge ) { 
        updateBadgeRepeater ( loggedInMember . _id ) 
        $w ( '#myBadges' ). show () 
        $w ( '#myBadges' ). expand () 

        **let**  PatronBadge  =  **await**  getPatronBadge ( loggedInMember . _id ); 

        **if**  ( PatronBadge ) { 
            updateBadgeRepeater ( loggedInMember . _id ) 
            $w ( '#myBadges' ). show () 
            $w ( '#myBadges' ). expand () 
        } 
    } 

}); 

**function**  myBadges ( memberBadges ) { 
    wixData . query ( "Members/Badges" ) 
        . hasSome ( '_id' ,  memberBadges [ 0 ]. badgeIds ) 
        . find () 
        . then (( results ) => { 
            console . log ( results . items ) 
            $w ( '#myBadges' ). data  =  results . items ; 
            $w ( '#myBadges' ). onItemReady (( $item ,  itemData ,  index ) => { 
                $item ( "#badgeName" ). text  =  itemData . title ; 
                $item ( "#badgeRules" ). text  =  itemData . description ; 
                $item ( "#badgeIcon" ). src  =  itemData . icon ; 
            }) 
            $w ( '#myBadges' ). expand (); 
        }); 
} 

**function**  updateBadgeRepeater ( memberId ) { 
    getMemberBadges ( memberId ). then (( results ) => { 
        **if**  ( results . length  >  0 ) { 
            myBadges ( results ) 
        } 
    }) 

} 

})


And the backend:

import { badges } from ‘wix-members-backend’ ;

export function getMemberBadges ( memberID ) {

return badges . listMemberBadges ([ memberID ])
. then (( memberBadges ) => {
return memberBadges ;
})
. catch (( error ) => {
console . error ( error );
});
}

export function getTenuredBadge ( loggedInMember ) {
const badgeId = “2e93fa8d-a13f-4f2a-b47c-cbc16b903581” ;

return badges . assignMembers ( badgeId , [ loggedInMember ])
. then (( assignedMembers ) => {
return assignedMembers ;
})
. catch (( error ) => {
console . error ( error );
});
}

export function getPatronBadge ( member ) {
const badgeId = “2a36d9ae-a8de-409d-aae5-3007cb9e81a5” ;

return badges . assignMembers ( badgeId , [ member ])
. then (( assignedMembers ) => {
return assignedMembers ;
})
. catch (( error ) => {
console . error ( error );
});
}


Thanks again for your time on this. This is the last piece of the puzzle before we launch the site!

Okay so this is my interpretation of what you are seeing/asking

The user clicks a button
A series of if- statements will decide what badges to assign

The problem: Your if- statements are not working correctly so the badge assignment is incorrect

What the issue looks like to me:
Your if statements are disconnected, you need if/else or switch/case if you want them to be evaluated in connection to one another

If(criteria1){
//do something
else if (criteria 2) {
//do something else
}

Let me know if that interpretation seems correct.

Yes exactly.

Ideally I want the member to go to a page and then be assigned a badged based on a certain set of criteria.

The criteria for my first two badges were based on number of likes on a post, and number of views on a post.

Right now I cannot incorporate both the currently logged in member and the criteria into the code all at once.

I want this code to only run for the currently logged in member. So in the very first code screenshot I sent, the criteria were working properly, but it awarded the button to every single member who met the criteria every time anyone arrived on that page. This happened because my topPostOwners array was being sent to my assignMembers function, but I can’t figure out how to extract the currently logged in member from that array and apply the assignMembers function only to them.

I’m not sure if I’m making it clear, so I’d like to offer an example:

Eric logs into the website. He made a forum post last week that has now reached over 50 likes.

There is a badge called “Tenured” that is awarded to the currently logged in member when they navigate to a page called “Claim Badges” if they are the owner of a post with more than 50 likes.

When Eric navigates to the"Claim Badges" page, the code runs to determine if the current member, Eric, is eligible for any badges.

If Eric already has the “Tenured” badge, the code will not reassign the badges.
If Eric does not have the “Tenured”, the code will check if he is eligible for that badge.

The code determines that Eric is eligible for the “Tenured” badge because he is the owner of a post with more than 50 likes.

Therefore, he is awarded that badge shortly after arriving on the “Claim Badges” page.

No members other than the currently logged in member are ever considered for any badges. This way they will not be awarded a badge more than once will not receive any notifications.

The exact same scenario happens simultaneously for other badges called “Patron” “Virtuoso” etc.

I know that’s a very long message… hope it’s clear! Thank you so much, Amanda!

So the first problem is that you have a click function that you don’t need it sounds like as you don’t want the users to have to perform an action to see their “leaderboard”

You may need to take a step back and build one part at a time to see where the error is happening.

In your page code you should first be getting the current member
then passing it to the jsw function from my tutorial to “getMemberBadges”

Once you are sure that function always returns as expected, you can move on to the next logic which sounds like…

if (userHasAllAvailableBadges) {
//do nothing
} else if (user has < AllAvailableBadges) {
//check criteria for badges user is missing
//assign whichever badge ids are missing
//refresh repeater to show newly assigned badges
}

What I would suggest is going through each step and debugging using console.log() and when each step is correct, then move on

So first make sure you are always able to get and display the users current badges on page load

Next, make sure you can identify when a user does not have all of the possible badges

Next, test your logic to check if user meets new badge criteria

Then finally if everything is working consistently so far - assign new badges (if applicable) and refresh the repeater.

Sometimes when you write ALL the code at once, it’s hard to identify the exact problem. So I would comment things out and methodically go through one piece at a time to debug the code. Debugging is really the only way to figure out where the problem is.

This is all super helpful! I appreciate the info as it will help me with other projects in the future as well.

I’ve just tried again, but no luck so far. It seems there is one part of the code I am really struggling with. Right now the only way I know how to set the criteria for the badge is what is shown in the API reference, which gives an entire array of ID’s instead of a single member ID. This is what I have:

//Query the forum posts to find the owners of posts with more than 50 likes.

wixData.query("Forum/Posts")
.gt("likeCount", 50)
.find()
.then((results) => {
if(results.items.length > 0){
const topPosts = results.items;

// Then Assign those post owner ID's to an array that is passed to the backend function which will assignMembers badges

const topPostOwners = topPosts.map(post => post._ownerId);
assignMembersBackendFunction(TenuredBadgeId, topPostOwners);

//
The problem here is that I don't know how to only send the currently logged-in member ID to the backend function... I can only seem to find examples where the query produces an array of Member Id's which I seemingly cannot continue to filter down to a single member.

My knowledge is sadly limited and I’m really struggling to understand how to incorporate specific Badge criteria into the code without producing an entire array.

Is it possible to further query the array?

I need to determine if the currently logged in member is in the array, and then if so, send their memberID to the backend function?

Maybe there’s a different way to do the original query altogether?

Thanks Amanda!

@percussionconservato I think I understand and you may be going about this backwards.

First to get the currently loggedin member, you will need to do this in your onready function

import { currentMember } from 'wix-members';

const options = {
    fieldsets: ['FULL']
}

$w.onReady(async function () {

    const loggedInMember = await currentMember.getMember(options);
    }

This will only ever give you the id of the logged in member that is visiting that page

You can then pass this member ID to your query something like this (untested by me so play around if it’s not quite right but what you are doing is saying pull all forum posts where ownerID = logged in user AND likes are over 50

wixData.query("Forum/Posts")
.eq("OwnerId","loggedInMemberID")
.gt("likeCount", 50)
.find()
.then((results) => {

If this query of your forum posts comes back with nothing, then your currently logged in member does not have any posts with likes over 50.

If the query comes back with posts, then you can send that single member id to the backend.

Will that work for your use case?