Skip to content

Commit

Permalink
✨ Add new package specific to Pro features (#2870)
Browse files Browse the repository at this point in the history
* Pro: 🚧 Look at Pro as different package w/ imports

* 🚧 Move over tests and remove extra deps in JS lib

* ✅ Fix tests for CI

* ➖ Remove unused deps

* 🎨 Fix imports and remove commented code

* 🎨 Update to match correct JS class syntax
  • Loading branch information
chuckcarpenter committed Jun 18, 2024
1 parent 44e7b57 commit e148b5b
Show file tree
Hide file tree
Showing 25 changed files with 12,119 additions and 9,508 deletions.
2 changes: 1 addition & 1 deletion docs-src/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
"@astrojs/check": "^0.7.0",
"@astrojs/starlight": "^0.23.2",
"@astrojs/tailwind": "^5.1.0",
"astro": "^4.8.4",
"astro": "^4.8.6",
"sharp": "^0.33.3",
"shepherd.js": "workspace:*",
"starlight-typedoc": "^0.10.1",
Expand Down
2 changes: 0 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,14 +36,12 @@
"@typescript-eslint/eslint-plugin": "^7.5.0",
"@typescript-eslint/parser": "^7.5.0",
"autoprefixer": "^10.4.19",
"better-docs": "^2.7.3",
"concurrently": "^8.2.2",
"del": "^7.1.0",
"eslint": "^8.57.0",
"eslint-plugin-jest": "^28.5.0",
"eslint-plugin-svelte": "^2.39.0",
"postcss": "^8.4.38",
"postinstall-postinstall": "^2.1.0",
"prettier": "3.1.1",
"prettier-plugin-svelte": "^3.2.2",
"release-it": "^17.3.0",
Expand Down
13 changes: 13 additions & 0 deletions packages/pro-js/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# @shepherdpro/pro-js

## 0.0.3

### Patch Changes

- Fix exports for published packages

## 0.0.2

### Patch Changes

- Improve types and reusing the Pro JS module
56 changes: 56 additions & 0 deletions packages/pro-js/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Shepherd Pro

Unlock additional capabilities and more full featured Journeys.

* **Customizable Tour Templates**: Pre-designed templates for different types of software that can be easily customized.
* **Analytics Integration**: To track user engagement and effectiveness of the tours.
* **Multi-Language Support**: For global reach and accessibility.
* **User Behavior Tracking**: To understand how users interact with the tours and optimize accordingly.
* **Integration Capabilities**: Easy integration with a wide range of web applications and software.
* **Responsive Design**: Ensuring tours work seamlessly on all devices.
* **Feedback Mechanisms**: Allowing users to provide feedback directly within the tours.
* **Advanced Branching Logic**: For personalized tour experiences based on user actions or profiles.

# Demo
See Shepherd Live on our docs website by clicking on the image:
<a href="https://blog.shepherdpro.com/demo">
<img
src="https://blog.shepherdpro.com/img/demo.png"
alt="Guide your users through a tour of your app"
style="height: auto; max-width: 800px; width: 100%;"/>
</a>

## Install Directly

```bash
npm install @shepherdpro/pro-js --save
```

```bash
yarn add @shepherdpro/pro-js
```

```bash
pnpm add @shepherdpro/pro-js
```

```bash
bun add @shepherdpro/pro-js
```

### Shepherd Pro hosted SaaS (Alpha)

You can try our hosted version for free at [https://shepherdpro.com/](https://shepherdpro.com)

The Entire Shepherd Service that powers Shepherd Pro is the newest iteration of Shepherd and includes the features of Shepherd Pro.

# White Glove Services

If you have an idea or project in mind and would like to engage our team to build a custom tour, training or on-boarding experience, get in touch! [hello@shepherdpro.com](mailto:hello@shepherdpro.com)

## Resources

- [Website](https://shepherdpro.com/)
- [Documentation](https://docs.shepherdpro.com/)
- [Demo](https://blog.shepherdpro.com/demo/)
- If you have any questions about our projects you can email [hello@shepherdpro.com](mailto:hello@shepherdpro.com)
41 changes: 41 additions & 0 deletions packages/pro-js/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"name": "@shepherdpro/pro-js",
"version": "0.0.3",
"private": false,
"main": "./dist/index.umd.cjs",
"module": "./dist/index.js",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.js",
"require": "./dist/index.umd.cjs"
},
"./package.json": "./package.json"
},
"type": "module",
"files": [
"dist",
"src"
],
"scripts": {
"build": "vite build",
"test:ci": "vitest --run",
"test:dev": "vitest"
},
"devDependencies": {
"@vitest/ui": "^1.6.0",
"fake-indexeddb": "^5.0.2",
"happy-dom": "^12.10.3",
"vite": "^5.2.11",
"vite-plugin-dts": "^3.7.1",
"vitest": "^1.6.0"
},
"dependencies": {
"shepherd.js": "workspace:*",
"idb": "^8.0.0"
},
"peerDependencies": {
"typescript": "^5.0.0"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ interface TourStateDb extends DBSchema {
}

class DataRequest {
apiKey: string;
apiPath: string;
properties?: { [key: string]: unknown };
#apiKey: string;
#apiPath: string;
#properties?: { [key: string]: unknown };
tourStateDb?: IDBPDatabase<TourStateDb>;

constructor(
Expand All @@ -29,19 +29,27 @@ class DataRequest {
throw new Error('Shepherd Pro: Missing required apiPath option.');
}

this.apiKey = apiKey;
this.apiPath = apiPath;
this.properties = properties;
this.#apiKey = apiKey;
this.#apiPath = apiPath;
this.#properties = properties;
}

getConfig() {
return {
apiKey: this.#apiKey,
apiPath: this.#apiPath,
properties: this.#properties
};
}

/**
* Gets a list of the state for all the tours associated with a given apiKey
*/
async getTourState() {
try {
const response = await fetch(`${this.apiPath}/api/v1/state`, {
const response = await fetch(`${this.#apiPath}/api/v1/state`, {
headers: {
Authorization: `ApiKey ${this.apiKey}`,
Authorization: `ApiKey ${this.#apiKey}`,
'Content-Type': 'application/json'
},
method: 'GET'
Expand Down Expand Up @@ -82,12 +90,12 @@ class DataRequest {
* @param body The data to send for the event
*/
async sendEvents(body: { data: Record<string, unknown> }) {
body.data['properties'] = this.properties;
body.data['properties'] = this.#properties;

try {
const response = await fetch(`${this.apiPath}/api/v1/actor`, {
const response = await fetch(`${this.#apiPath}/api/v1/actor`, {
headers: {
Authorization: `ApiKey ${this.apiKey}`,
Authorization: `ApiKey ${this.#apiKey}`,
'Content-Type': 'application/json'
},
method: 'POST',
Expand Down
148 changes: 148 additions & 0 deletions packages/pro-js/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
import Shepherd, { ShepherdBase } from 'shepherd.js';
import type { TourOptions, EventOptions } from 'shepherd.js';

import DataRequest from './DataRequest';
import { getContext } from './utils/context.ts';

interface Actor {
actorId: number;
}

const SHEPHERD_DEFAULT_API = 'https://shepherdpro.com' as const;
const SHEPHERD_USER_ID = 'shepherdPro:userId' as const;

class ProTour extends Shepherd.Tour {
public events = ['active', 'cancel', 'complete', 'show'];

private currentUserId: string | null = null;

constructor(options?: TourOptions) {
super(options);

this.currentUserId = localStorage.getItem(SHEPHERD_USER_ID);

const { dataRequester } = ShepherdProInstance;

if (dataRequester) {
this.trackedEvents.forEach((event) =>
this.on(event, (opts: EventOptions) => {
const { tour } = opts;
const { id, steps } = tour;
let position;

if (event !== 'active') {
const { step: currentStep } = opts;

if (currentStep) {
position =
steps.findIndex((step) => step.id === currentStep.id) + 1;
}
}

const data = {
currentUserId: this.currentUserId,
eventType: event,
journeyData: {
id,
currentStep: position,
numberOfSteps: steps.length,
tourOptions: tour.options
}
};
dataRequester?.sendEvents({ data });
})
);
}
}
}

export class ShepherdPro extends ShepherdBase {
// Shepherd Pro fields
apiKey?: string;
apiPath?: string;
dataRequester?: DataRequest;
isProEnabled = false;
/**
* Extra properties to pass to Shepherd Pro App
*/
properties?: { [key: string]: unknown };

constructor() {
super();

this.Tour = ProTour;
}

/**
* Call init to take full advantage of ShepherdPro functionality
* @param {string} apiKey The API key for your ShepherdPro account
* @param {string} apiPath
* @param {object} properties Extra properties to be passed to Shepherd Pro
*/
async init(
apiKey?: string,
apiPath?: string,
properties?: { [key: string]: unknown }
) {
if (!apiKey) {
throw new Error('Shepherd Pro: Missing required apiKey option.');
}

this.apiKey = apiKey;
this.apiPath = apiPath ?? SHEPHERD_DEFAULT_API;
this.properties = properties ?? {};
this.properties['context'] = getContext(window);

if (this.apiKey) {
this.dataRequester = new DataRequest(
this.apiKey,
this.apiPath,
this.properties
);

// Setup actor before first tour is loaded if none exists
const shepherdProId = localStorage.getItem(SHEPHERD_USER_ID);

const promises = [this.dataRequester.getTourState()];

if (!shepherdProId) {
promises.push(this.createNewActor());
}

await Promise.all(promises);
}
}

async createNewActor() {
if (!this.dataRequester) return;

// Setup type returns an actor
const response = (await this.dataRequester.sendEvents({
data: {
currentUserId: null,
eventType: 'setup'
}
})) as unknown as Actor;

localStorage.setItem(SHEPHERD_USER_ID, String(response.actorId));
}

/**
* Checks if a given tour's id is enabled
* @param tourId A string denoting the id of the tour
*/
async isTourEnabled(tourId: string) {
if (!this.dataRequester) return;

const tourState = await this.dataRequester.tourStateDb?.get(
'tours',
tourId
);

return tourState?.isActive ?? true;
}
}

const ShepherdProInstance = new ShepherdPro();

export default Object.assign(ShepherdProInstance, Shepherd) as ShepherdPro;
File renamed without changes.
Loading

0 comments on commit e148b5b

Please sign in to comment.