🚧

Preferences changes in @suprsend/web-sdk version >=2.0.0.

In @suprsend/[email protected] we have revamped SDK. The whole structure of preferences have been changed from SDK side. Please upgrade to newer versions. Old version of documentation for preference is available here.

This documentation is sample example implementation of @suprsend/web-sdk in Angular applications.

Github Repository for this example implementation can be found here: https://github.com/suprsend/angular-example

Pre-requisites

Integration

There will be 3 components in example implementation:

  • preference
  • channel-level-preferences
  • category-level-preferences

Preference component setup

preference will be the main component and inside it, there will be a property (preferencesData) that contains full user preference data. Whenever there's an update we will update the latest preference data in the preferencesData so that component to rerender. We already created SuprSend service to access SuprSend client everywhere in the application which is used to call methods.

Call getPreferences method to get the preferences data for the already identified user. This method is used to get full user preferences data from the SuprSend. This method should be called first before any update methods. Calling this method will make an API call and returns a preference response, which you can store in your instance property preferenceData if there is no error.

After that we configure 2 event listeners preferences_updated and preferences_error to listen to updates in preference data and errors.

Our Main component preference has 2 child components one for Category-level preference section and other for Overall Channel-level preference section.

import { SuprsendService } from '../suprsend.service';

@Component({...})
export class PreferenceComponent implements OnInit {
  preferencesData: any = null;
  
  constructor(private router: Router, private ssService: SuprsendService) {}

  ngOnInit(): void {
    // before getting preferences make sure to call identify method
    this.ssService.ssClient.user.preferences.getPreferences().then((resp) => {
      if (resp.status === 'error') {
        console.log(resp.error);
      } else {
        this.preferencesData = resp.body;
      }
    });
		
    // listen for update in preferences data
    this.ssService.ssClient.emitter.on(
      'preferences_updated',
      (preferenceData) => { this.preferencesData = { ...preferenceData.body } } );

    // listen for errors
    this.ssService.ssClient.emitter.on('preferences_error', (error) => {
      console.log('ERROR:', error);
    });
  }
}
<p *ngIf="!this.preferencesData">Loading...</p>

<div *ngIf="this.preferencesData" class="main-div">
  <h3 class="main-header">Notification Preferences</h3> 

  <!-- notification category level preferences -->
  <app-category-level-preferences [preferencesData]="this.preferencesData"></app-category-level-preferences>

  <!-- overall channel level preferences -->
  <app-channel-level-preferences [preferencesData]="this.preferencesData"></app-channel-level-preferences>
</div>
.main-div {
  margin: 24px;
}

.main-header {
  margin-bottom: 24px;
}

p,
h3 {
  margin: 0;
  padding: 0;
  font-family: Arial, Helvetica, sans-serif;
}

Category level Preference section

In category-level preferences, you'll have to fetch the data from 3 parts:

  • Section - to show sections like "Product Updates" in below example
  • Category - to show categories and their overall status like "Refunds" in below example
  • CategoryChannel - to show communication channels inside the category and their status


Below are the steps to render category preference UI:

  1. Loop through the property preferenceData.sections for showing sections, show sub-categories inside each section, and show subcategory's channels inside each sub-category.

  2. Add a switch button next to each sub-category for opting in and out of the category. Add checkbox components in sub-category channels for opting in and out of category-channel. You can use any third-party npm package to import these components or design your own component.

  3. To update category preference on the click of the switch button, call updateCategoryPreference method and if no error is received in response, update the latest data in the instance property. For preference state opt-in set the switch state as on and off for the opt-out state.

  1. To update category-channel preference on the click of checkbox next to each channel, call the updateChannelPreferenceInCategory method. Update the latest data in the instance property if no error is received in response. For preference state opt-in set the checkbox state as checked and unchecked for the opt-out state.

import { Component, Input } from '@angular/core';
import { PreferenceOptions } from '@suprsend/web-sdk';
import { SuprsendService } from '../suprsend.service';

@Component({
  selector: 'app-category-level-preferences',
  templateUrl: './category-level-preferences.component.html',
  styleUrls: ['./category-level-preferences.component.css'],
})
export class CategoryLevelPreferencesComponent {
  @Input() public preferencesData: any;
  
  constructor(private ssService: SuprsendService) {}

  async handleCategoryPreferenceChange(e: boolean, subcategory: string) {
    const resp =
      await this.ssService.ssClient.user.preferences.updateCategoryPreference(
        subcategory,
        e ? PreferenceOptions.OPT_IN : PreferenceOptions.OPT_OUT
      );
    if (resp.error) {
      console.log(resp.error);
    } else {
      this.preferencesData = { ...resp.body };
    }
  }

  async handleChannelPreferenceInCategoryChange(
    channel: any,
    category: string
  ) {
    if (!channel.is_editable) return;

    const resp =
      await this.ssService.ssClient.user.preferences.updateChannelPreferenceInCategory(
        channel.channel,
        channel.preference === PreferenceOptions.OPT_IN
          ? PreferenceOptions.OPT_OUT
          : PreferenceOptions.OPT_IN,
        category
      );
    if (resp.status === 'error') {
      console.log(resp.error);
    } else {
      this.preferencesData = { ...resp.body };
    }
  }
}
<div
  *ngFor="let section of this.preferencesData.sections"
  class="cat-container"
>
  <div *ngIf="section.name">
    <div class="section-name-container">
      <p class="section-name-text">{{ section.name }}</p>
      <p class="section-description-text">{{ section.description }}</p>
    </div>
  </div>
  <div *ngFor="let subcategory of section.subcategories">
    <div class="subcategory-container">
      <div class="subcategory-top-div">
        <div>
          <p class="subcategory-name">{{ subcategory.name }}</p>
          <p class="subcategory-description">{{ subcategory.description }}</p>
        </div>
        <ui-switch
          size="small"
          color="#2463eb"
          [disabled]="!subcategory.is_editable"
          [checked]="subcategory.preference === 'opt_in'"
          (change)="
            handleCategoryPreferenceChange($event, subcategory.category)
          "
        ></ui-switch>
      </div>

      <div class="subcategory-channel-container">
        <div
          *ngFor="let channel of subcategory.channels"
          class="category-channel-checkbox"
        >
          <input
            type="checkbox"
            [id]="subcategory.category + '-' + channel.channel"
            [disabled]="!channel.is_editable"
            [checked]="channel.preference === 'opt_in'"
            (change)="
              handleChannelPreferenceInCategoryChange(
                channel,
                subcategory.category
              )
            "
          />
          <label
            class="category-channel-label"
            [for]="subcategory.category + '-' + channel.channel"
            >{{ channel.channel }}</label
          >
        </div>
      </div>
    </div>
  </div>
</div>

.cat-container {
  margin-bottom: 24px;
}

.section-name-container {
  background-color: #fafbfb;
  padding-top: 12px;
  padding-bottom: 12px;
  margin-bottom: 18px;
}

.section-name-text {
  font-size: 18px;
  font-weight: 500;
  color: #3d3d3d;
}

.section-description-text {
  color: #6c727f;
}

p,
label {
  margin: 0;
  padding: 0;
  font-family: Arial, Helvetica, sans-serif;
}

.subcategory-channel-container {
  display: flex;
  gap: 10;
  margin-top: 12px;
}

.subcategory-name {
  font-size: 16px;
  font-weight: 600;
  color: #3d3d3d;
}

.subcategory-top-div {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.subcategory-container {
  border-bottom: 1px solid #d9d9d9;
  padding-bottom: 12px;
  margin-top: 18px;
}

.subcategory-description {
  color: #6c727f;
  font-size: 14px;
  margin-top: 6px;
}

.category-channel-checkbox {
  margin-right: 20px;
}

.category-channel-label {
  margin-left: 4px;
  cursor: pointer;
}

Channel level Preference section

Below are the steps to render channel preference UI:

  1. Loop through the property preferenceData.channel_preferences for showing channels and for every channel item we will show an option to select preference using radio buttons.

  2. Add a radio button next, against channel level options for switching from all to required preference in channel.

  3. To update channel preference on the click of the radio button, call updateOverallChannelPreference method, and if no error is received in response, update the latest data in the state.

import { Component, Input } from '@angular/core';
import { ChannelLevelPreferenceOptions } from '@suprsend/web-sdk';
import { SuprsendService } from '../suprsend.service';

@Component({
  selector: 'app-channel-level-preferences',
  templateUrl: './channel-level-preferences.component.html',
  styleUrls: ['./channel-level-preferences.component.css'],
})
export class ChannelLevelPreferencesComponent {
  @Input() public preferencesData: any;

  constructor(private ssService: SuprsendService) {}

  async handleChange(channel: string, preference: string) {
    const preferenceStatus =
      preference === 'ALL'
        ? ChannelLevelPreferenceOptions.ALL
        : ChannelLevelPreferenceOptions.REQUIRED;
    const resp =
      await this.ssService.ssClient.user.preferences.updateOverallChannelPreference(
        channel,
        preferenceStatus
      );
    if (resp.status === 'error') {
      console.log(resp.error);
    } else {
      this.preferencesData = { ...resp.body };
    }
  }
}

<div>
  <div class="channel-header-div">
    <p class="channel-header-p">What notifications to allow for channel?</p>
  </div>
  <div>
    <div *ngFor="let channel of this.preferencesData.channel_preferences">
      <div class="channel-container">
        <p class="channel-channel-text">{{ channel.channel }}</p>
        <p
          class="channel-help-text"
          *ngIf="channel.is_restricted; else allText"
        >
          Allow required notifications only
        </p>
        <ng-template #allText
          ><p class="channel-help-text">Allow all notifications</p>
        </ng-template>

        <div class="channel-radio-container">
          <p class="channel-radio-pref-text">
            {{ channel.channel }} Preferences
          </p>
          <div class="radio-grp">
            <div class="radio-grp-2">
              <div class="radio-grp-container">
                <div>
                  <input
                    type="radio"
                    [checked]="!channel.is_restricted"
                    name="all-{{ channel.channel }}"
                    id="all-{{ channel.channel }}"
                    (change)="handleChange(channel.channel, 'ALL')"
                  />
                </div>
                <label class="all-label" for="all-{{ channel.channel }}">
                  All
                </label>
              </div>
              <p class="channel-radiohelp-text">
                Allow All Notifications, except the ones that I have turned off
              </p>
            </div>
            <div>
              <div class="radio-grp-container">
                <div>
                  <input
                    type="radio"
                    name="required-{{ channel.channel }}"
                    id="required-{{ channel.channel }}"
                    [checked]="channel.is_restricted"
                    (change)="handleChange(channel.channel, 'REQUIRED')"
                  />
                </div>
                <label
                  class="required-label"
                  for="required-{{ channel.channel }}"
                >
                  Required
                </label>
              </div>
              <p class="channel-radiohelp-text">
                Allow only important notifications related to account and
                security settings
              </p>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>

.channel-header-div {
  background-color: #fafbfb;
  padding-top: 12px;
  padding-bottom: 12px;
  margin-bottom: 18px;
}

.channel-header-p {
  font-size: 18px;
  font-weight: 500;
  color: #3d3d3d;
}

p,
label {
  margin: 0;
  padding: 0;
  font-family: Arial, Helvetica, sans-serif;
}

.channel-container {
  border: 1px solid #d9d9d9;
  border-radius: 5px;
  padding: 12px 24px;
  margin-bottom: 24px;
}

.channel-channel-text {
  font-size: 18px;
  font-weight: 500;
  color: #3d3d3d;
}

.channel-help-text {
  color: #6c727f;
  font-size: 14px;
  margin-top: 6px;
}

.channel-radio-container {
  margin-top: 12px;
  margin-left: 24px;
}

.channel-radio-pref-text {
  color: #3d3d3d;
  font-size: 16px;
  font-weight: 500;
  margin-top: 18px;
  border-bottom: 1px solid #e8e8e8;
}

.channel-radiohelp-text {
  color: #6c727f;
  font-size: 14px;
  margin-left: 32px;
  margin-top: 4px;
}

.label-required {
  margin-left: 12px;
}

.radio-grp {
  margin-top: 12px;
}

.radio-grp-2 {
  margin-bottom: 8px;
}

.radio-grp-container {
  display: flex;
  align-items: center;
}
.all-label {
  margin-left: 12px;
  cursor: pointer;
}

.required-label {
  margin-left: 12px;
  cursor: pointer;
}