Skip to main content

Advanced - Team Activities App

Ready to take things further? This advanced tutorial will take you step-by-step through building a Showpad App that enables people in your organization to create teams, join them, and propose activities.

app

The finished app will incorporate:

QuantityDescription
AppsDB Stores2These shared stores will hold:
  • the teams created by admins
  • the activities created by users
Extension Types3These will be rendered on various extension points on the Showpad platform:

While building this app, you’ll work with:

Description
Experience App SDKThis will enable you to leverage the capabilities of the Showpad platform.
Extension TypesThese will interact with each other through AppsDB.
Showpad CLIThis provides the ability to bundle and upload the app.
tip

You'll find the Team Activities app's code with all the files in this GitHub repository. Feel free to clone this repository and use it as a reference or compare it to the final version of the app you create.

info

No frameworks or UI kits are needed for this tutorial. The examples here use vanilla TypeScript to keep the app as light as possible. To scaffold, run, and build the app, we'll use Vite (you can use the tooling of your choice to achieve these tasks).

Prerequisites

Development

Showpad

  • Showpad Ultimate package
  • Administrator access to Showpad's Online Platform

1. Initialize App

Create App Folder

The first step is to use Vite to scaffold, run and build the app. This command will create the app folder and all of the necessary files:

npm create vite@latest team-activities-app

The process creates a file and a folder that aren't necessary at this point, so you should delete:

  • index.html file
  • src folder

Install NodeJS Dependencies

Next, it's time to install the NodeJS dependencies (Typescript and Vite).

In the team-activities-app folder, run the install command:

npm install

Install Experience App SDK

During development of your Showpad App, you'll use the Experience App SDK to interact with the Showpad API.

In the team-activities-app folder, run the the following command:

npm install @showpad/experience-app-sdk

Install Showpad CLI

The Showpad CLI is Showpad's robust command-line tool that helps streamline the development of Showpad Apps.

In the team-activities-app folder, run the the following command:

npm install -D @showpad/cli

Login to Showpad

In order to connect to your Showpad environment, you must run the login command and follow the prompt:

npx showpad login

Create Manifest

The Manifest defines your app's:

  • schema and version
  • public information
  • extension(s)

In your app's root folder, create a manifest.json file:

{
"$schema": "https://schema.showpad.com/app/manifest.v2.json",
"schema": {
"type": "app-manifest",
"version": 2
},
"appKey": "<your-app-key>",
"name": "Team Activities App",
"version": "0.0.1",
"description": {
"short": "The Team Activities App",
"full": "An App to propose some team activities for your next team events"
},
"developer": {
"name": "<your-developer-name>",
"websiteUrl": "<your-website-url>"
}
}

Create AppsDB stores

In order to store data in AppsDB, you must declare your stores and perform an initial upload of your app.

  • Add the following lines to your manifest.json file:

    {
    ...
    "sharedStores": [
    { "id": "teams-store" },
    { "id": "team-activities-store" }
    ]
    }
  • Bundle your app by running the bundle command:

    npx showpad-cli bundle
  • Upload your app to Showpad by running the upload command:

    npx showpad-cli apps upload <your-app-key>.0.0.1.showpad
  • Install your app in the Online Platform. See Install custom Showpad Apps for all the details.

2. Admin Settings Extension

The Admin Settings extension enables admins to create teams in their organizsations.

a_settings

Declare Extension

The Admin Settings extension will add a new entry within the Showpad Apps section of the Admin Settings menu on the Online Platform.

To define an Admin Settings extension for your app, add the following lines to the manifest.json file:

{
...
"adminSettings": [
{
"extensionKey": "team-activities-admin-settings",
"name": "Teams",
"description": "Setup your teams",
"resources": {
"folder": "admin-settings"
}
}
]
}

Create Extension Folder

Run the following command to create a folder for your Admin Settings extension:

mkdir admin-settings

Within this folder, add this vite.config.ts file:

admin-settings/vite.config.ts
import { defineConfig } from 'vite';

export default defineConfig({
base: './',
build: {
target: 'ES2022',
outDir: '../dist/admin-settings',
},
});

This file:

  • defines the EcmaScript version used to build the extension
  • specifies that built files are generated into the ../dist/admin-settings folder

Create Index

The index.html is the entry point of your extension. For this extension, this file defines a form to create a team and a table to list existing teams.

Create the following file in your Admin Settings folder:

index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="./src/style.css" />
<title>Team Activities App - Admin Settings</title>
</head>
<body>
<div class="app-wrapper">
<div class="section-title">Add a Team</div>
<form>
<input type="text" id="name" name="name" placeholder="Team Name" />
<button type="button" id="addTeamButton">Add a Team</button>
</form>

<div class="section-title">All Teams</div>
<div id="teamList"></div>
</div>

<script type="module" src="./src/main.ts"></script>
</body>
</html>

Create Script

The code for your extension resides in the src/main.ts file. For this extension, this file defines the functions to create a new team and render the team list.

Create the following file in your Admin Settings folder:

src/main.ts
import { Showpad } from '@showpad/experience-app-sdk';
import { AxiosInstance } from 'axios';
import { v4 as uuid } from 'uuid';

type Team = {
name: string;
};

const TEAMS_STORE_ID = 'teams-store';

let showpadInstance: AxiosInstance;

const addTeam = async () => {
try {
const nameField = document.querySelector<HTMLInputElement>('#name')!;
const team: Team = {
name: nameField.value || '',
};

if (team.name) {
await Showpad.setGlobalStoreEntryValue(TEAMS_STORE_ID, uuid(), team, showpadInstance);
nameField.value = '';

renderTeamList();

Showpad.displayToast({
type: 'success',
text: 'Team created',
});
}
} catch (error) {
Showpad.handleErrorWithToast(error);
console.error(error);
}
};

const renderTeamList = async () => {
try {
const teams = await Showpad.getGlobalStoreEntries<Team>(TEAMS_STORE_ID);
let teamListHtml = '';
teams.forEach((team) => (teamListHtml += `<tr><td>${team.value.name}</td></tr>`));
document.querySelector<HTMLDivElement>('#teamList')!.innerHTML = `<table><th>Team Name</th>${teamListHtml}</table>`;
} catch (error) {
Showpad.handleErrorWithToast(error);
console.error(error);
}
};

const main = async (): Promise<void> => {
try {
await Showpad.onShowpadLibLoaded();
showpadInstance = await Showpad.getShowpadInstance('v3');

// Render team list
renderTeamList();

// Bind CTAs
const btn = document.querySelector<HTMLButtonElement>('#addTeamButton');
btn?.addEventListener('click', () => addTeam());
} catch (error) {
Showpad.handleErrorWithToast(error);
console.error(error);
}
};

main();

Test Extension

Prerequisite

In order to test your extension locally, you’ll need to have an app exposing some Admin Settings installed in your account. If you don’t have any yet, you can install the app from the 5min Quick Start tutorial or ignore testing at this stage and continue with this tutorial.

In your app's root folder, run the following command:

npm run dev admin-settings

You can then:

  1. Open Admin Settings in the Online Platform

  2. Select Admin Settings Test from the Showpad Apps section

  3. Click the </> button:

    test

  4. Input the URL of your local server to render it within the Showpad platform:

    dev mode

3. User Settings Extension

The User Settings extension enables users to join teams.

u_settings

Declare Extension

The User Settings extension will add a new entry within the user's Settings menu in the Showpad Web app.

To define the User Settings extension for your app, add these lines to the manifest.json file:

{
...
"userSettings": [
{
"extensionKey": "team-activities-user-settings",
"name": "My Team",
"description": "Assign yourself to a team",
"resources": {
"folder": "user-settings"
}
}
]
}

Create Extension Folder

Run the following code to create a folder for your User Settings extension:

mkdir user-settings

Within this folder, add this vite.config.ts file:

user-settings/vite.config.ts
import { defineConfig } from 'vite';

export default defineConfig({
base: './',
build: {
target: 'ES2022',
outDir: '../dist/user-settings',
},
});

This file:

  • defines the EcmaScript version used to build the extension
  • specifies that built files are generated into the ../dist/admin-settings folder

Create Index

The index.html is the entry point of your extension. For this extension, this file defines a form for users to select a team.

Create the following file in your User Settings folder:

index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="./src/style.css" />
<title>Team Activities App - User Settings</title>
</head>
<body>
<div class="app-wrapper">
<form>
<div class="section-title">Select your team</div>
<select id="myTeam" name="myTeam"></select>
</form>
</div>

<script type="module" src="./src/main.ts"></script>
</body>
</html>

Create Script

The code of the extension resides in the src/main.ts file. For this extension, this file defines the functions to store the selected team.

Create the following file in your User Settings folder:

src/main.ts
import { Showpad } from '@showpad/experience-app-sdk';

type Team = Showpad.Scalar & {
name: string;
location: string;
department: string;
};

const TEAMS_STORE_ID = 'teams-store';
const MY_TEAM_ENTRY_ID = 'myTeamId';

const myTeamDropdownElement = document.querySelector<HTMLSelectElement>('#myTeam')!;

const populateTeamDropdown = async (): Promise<void> => {
let optionsHtml = '<option>Select your team</option>';
const myTeamId = await Showpad.getStoreEntryValue<Team>(TEAMS_STORE_ID, MY_TEAM_ENTRY_ID);
const teams = await Showpad.getGlobalStoreEntries<Team>(TEAMS_STORE_ID);
teams.forEach(
(team) => (optionsHtml += `<option value="${team.id}" ${myTeamId === team.id ? 'selected' : ''}>${team.value.name}</option>`),
);
myTeamDropdownElement.innerHTML = optionsHtml;
};

const setMyTeam = async (): Promise<void> => {
try {
await Showpad.setStoreEntryValue(TEAMS_STORE_ID, MY_TEAM_ENTRY_ID, myTeamDropdownElement.value);
Showpad.displayToast({
type: 'success',
text: 'Your team has been set',
});
} catch (error) {
Showpad.handleErrorWithToast(error);
console.error(error);
}
};

const main = async (): Promise<void> => {
try {
await Showpad.onShowpadLibLoaded();

// Populate the team dropdown
populateTeamDropdown();

// Bind CTAs
myTeamDropdownElement.addEventListener('change', () => setMyTeam());
} catch (error) {
Showpad.handleErrorWithToast(error);
console.error(error);
}
};

main();

Test Extension

Prerequisite

In order to test your extension locally, you’ll need to have an app exposing some User Settings installed in your account. If you don’t have any yet, you can install the app from the 5min Quick Start tutorial or ignore testing at this stage and continue with this tutorial.

In your app's root folder, run the following command:

npm run dev user-settings

You can then:

  1. Open Settings in the Web app

  2. Select User Settings Test in the left menu

  3. Click the </> button:

    user settings test

  4. Input the URL of your local server to render it within the Showpad platform:

    dev mode

4. Experience Type Extension

The Experience Type extension will enable users to propose activities for the team events.

experience

Declare Extension

The Experience Type extension will add a new type of Experience on the Create New Experiences dialog in the Online Platform.

To define the Experience Type extension for your app, add these lines to the manifest.json file:

{
...
"experienceTypes": [
{
"extensionKey": "team-activities-experience-type",
"name": "Team Activities",
"description": "Propose your team activities",
"resources": {
"folder": "experience"
}
}
]
}

Create Extension Folder

Run the following code to create a folder for your Experience Type extension:

mkdir experience

Within this folder, add a vite.config.ts file:

experience/vite.config.ts
import { defineConfig } from 'vite';

export default defineConfig({
base: './',
build: {
target: 'ES2022',
outDir: '../dist/experience',
},
});

This file:

  • defines the EcmaScript version used to build the extension
  • specifies that built files are generated into the ../dist/admin-settings folder

Define Experience Config

The configuration of your Experience Type extension is defined in the config.json file. This file:

  • specifies the content shown in your Experiences (e.g., assets, labels, tags, etc.)
  • contains properties that can be overwritten by an admin when creating an Experience through the Experience App builder

For this tutorial, we've defined two labels and an asset. When creating an Experience from this extension, the admin will be able to input the labels and pick the logo for the Experience.

You can define the configuration properties in the public/config.json file:

{
"version": 1,
"labels": {
"intro": {
"part1": "Here you can propose some activities for your next Team event.",
"part2": "Don't hesitate, go nuts!"
}
},
"contents": {
"logo": { "type": "asset" }
}
}

Create Index

The index.html is the entry point of your extension. For this tutorial, we've defined:

  • placeholders to render a greeting with information coming from the config.json file
  • a form to propose a team activity
  • a table to list all proposed activities

Create the following file in your Experience folder:

index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="./src/style.css" />
<title>Team Activities App - Experience</title>
</head>
<body>
<div class="app-wrapper">
<div id="logoWrapper" class="logo-wrapper"></div>

<div id="userGreetings" class="user-greetings"></div>
<div id="myTeam" class="my-team"></div>

<div class="intro-wrapper">
<div id="introPart1" class="intro"></div>
<div id="introPart2" class="intro"></div>
</div>

<div id="teamActivityCrud">
<div class="section-title">Propose a Team Activity</div>
<form>
<textarea
type="text"
id="teamActivityDescription"
name="description"
placeholder="Write your team activity proposal here"
></textarea>

<button type="button" id="addTeamActivityButton">Add a Team Activity</button>
<input type="checkbox" id="teamActivityAnonymous" name="anonymous" />
<label for="teamActivityAnonymous">Anonymous</label>
</form>

<div class="section-title">All Team Activities</div>
<div id="teamActivityList"></div>
</div>
</div>

<script type="module" src="./src/main.ts"></script>
</body>
</html>

Create Script

The code of the extension resides in the src/main.ts file. For this extension, this file defines the functions to render the information coming from the config.json, to create a new team activity and to render the activity list.

Create the following file in your Experience folder:

src/main.ts
import { Showpad } from '@showpad/experience-app-sdk';
import { AxiosInstance } from 'axios';
import { v4 as uuid } from 'uuid';

type TeamActivity = {
description: string;
teammate: string;
teamId: string;
};

type Team = {
name: string;
};

const TEAM_ACTIVITIES_STORE_ID = 'team-activities-store';
const TEAMS_STORE_ID = 'teams-store';
const MY_TEAM_ENTRY_ID = 'myTeamId';

let showpadInstance: AxiosInstance;
let myTeamId: string;
let myTeam: Team;

const addTeamActivity = async () => {
try {
const teamActivity: TeamActivity = {
description: document.querySelector<HTMLInputElement>('#teamActivityDescription')?.value || '',
teammate: document.querySelector<HTMLInputElement>('#teamActivityAnonymous')!.checked
? ''
: (await Showpad.getUserInfo()).fullName,
teamId: myTeamId?.toString(),
};

if (teamActivity.description && teamActivity.teamId) {
await Showpad.setGlobalStoreEntryValue(TEAM_ACTIVITIES_STORE_ID, uuid(), teamActivity, showpadInstance);
}

renderTeamActivityList();
} catch (error) {
Showpad.handleErrorWithToast(error);
console.error(error);
}
};

const renderTeamActivityList = async () => {
const teamActivities = (await Showpad.getGlobalStoreEntries<TeamActivity>(TEAM_ACTIVITIES_STORE_ID)).filter(
(teamActivity) => teamActivity.value.teamId === myTeamId,
);
let teamActivityListHtml = '';
teamActivities.forEach(
(teamActivity) =>
(teamActivityListHtml += `<tr><td>${teamActivity.value.description}</td><td>${teamActivity.value.teammate}</td></tr>`),
);
document.querySelector<HTMLDivElement>('#teamActivityList')!.innerHTML = `
<table>
<tr><th>Description</th><th>Teammate</th></tr>
${teamActivityListHtml}
</table>`;
};

const showGreetings = async (): Promise<void> => {
const userInfo = await Showpad.getUserInfo();
document.querySelector<HTMLDivElement>('#userGreetings')!.innerHTML = `Hello ${userInfo.fullName}!`;
};

const showLogoAndIntro = async (): Promise<void> => {
const config = await Showpad.parseEnrichedConfig();
document.querySelector<HTMLDivElement>('#introPart1')!.innerHTML = config.labels.intro.part1.value;
document.querySelector<HTMLDivElement>('#introPart2')!.innerHTML = config.labels.intro.part2.value;
document.querySelector<HTMLDivElement>('#logoWrapper')!.innerHTML = `<img src="${
config.assets[config.contents.logo.value].previewUrl
}"/>`;
};

const initMyTeam = async (): Promise<void> => {
const myTeamDomElement = document.querySelector<HTMLDivElement>('#myTeam')!;
myTeamId = await Showpad.getStoreEntryValue(TEAMS_STORE_ID, MY_TEAM_ENTRY_ID);
if (myTeamId) {
myTeam = await Showpad.getGlobalStoreEntryValue<Team>(TEAMS_STORE_ID, myTeamId);
if (myTeam) {
myTeamDomElement.innerHTML = `Your team is <strong>${myTeam.name}</strong>`;
document.querySelector<HTMLDivElement>('#teamActivityCrud')!.style.display = 'block';
} else {
myTeamDomElement.innerHTML = "Your team doesn't exist anymore";
}
} else {
myTeamDomElement.innerHTML = 'Please select your team in the "My Team" user settings';
}
};

const main = async (): Promise<void> => {
try {
// Wait for the Showpad SDK to be initialized
await Showpad.onShowpadLibLoaded();
showpadInstance = await Showpad.getShowpadInstance('v3');

// Show greetings
await showGreetings();

// Show logo and intro
await showLogoAndIntro();

// Init
await initMyTeam();

if (myTeamId) {
// Render team activity list
renderTeamActivityList();

// Bind event listeners
document.querySelector<HTMLButtonElement>('#addTeamActivityButton')!.addEventListener('click', () => addTeamActivity());
}
} catch (error) {
Showpad.handleErrorWithToast(error);
console.error(error);
}
};

main();

Test Extension

PREREQUISITE

In order to test your extension locally, you’ll need to have an app exposing some Experience types installed in your account. If you don’t have any yet, you can install the app from the 5min Quick Start tutorial or ignore testing at this stage and continue this tutorial.

In your app's root folder, run the following command:

npm run dev experience

You’ll then need to create an Experience from the Experience Test type as described here: Install custom Showpad Apps

Once your Experience is published, you can:

  1. Navigate to Experiences on the Web app

  2. Select the Experience you’ve just created

  3. Click the </> button:

    exp dev

  4. Input the URL of your local server to render it within the Showpad platform:

    dev mode

5. Build App

  • Before building your app, you need to replace the build script in the package.json with this one:

    {
    "scripts": {
    ...
    "build": "tsc && vite build admin-settings && vite build user-settings && vite build experience",
    ...
    }
    }
  • Then in your app's root folder, run the following command:

        npm run build

    This will generate a dist folder containing the resources of your extensions.

6. Bundle App

  • Increase the version of your app in the manifest.json to 0.0.2 and add this script to the package.json:

    {
    "scripts": {
    ...
    "prebundle": "cp manifest.json dist && cp -r images dist",
    ...
    }
    }
  • Then, run the bundle command:

    npm run bundle
  • Next, generate a Showpad package of your app by running the following command:

    npx showpad-cli apps bundle -s dist

    This will generate the <your-app-key>.0.0.2.showpad file in your app's root folder.

7. Upload to Showpad

In your app's root folder, run the following command:

npx showpad-cli apps upload <your-app-key>.0.0.2.showpad

8. Install your App

Now is the moment you've been working for! It's time to install your app through the Online Platform.

install

9. Test App

As soon as your app is installed, you should see the following enhancements in your Showpad instance:

  • A new Teams app in the Showpad Apps section of Admin Settings in the Online Platform

    teams app

  • A new Team Activities option when creating an Experience

    teams app

  • A new My Team entry in users' Settings on the Showpad Web app

    teams app