Skip to content

Repository files navigation

StackOne HUB

LicenseNode Version

StackOne HUB is a React-based integration component library that provides a web component wrapper for seamless integration into any web application. It enables developers to easily embed StackOne's integrations hub.

📋 Table of Contents

🚀 Quick Start

# Clone and setup
git clone <repository-url>cd hub
npm install
npm run build
# Start development
npm run dev

📦 Installation

Prerequisites

  • Node.js v22.14.0 or higher
  • npm (comes with Node.js)

Setup

  1. Clone the repository:

    git clone <repository-url>cd hub
  2. Install dependencies:

    npm install
  3. Build the project:

    npm run build

🛠️ Development

Environment Setup

  1. Create environment file:

    cp .env.example .env
  2. Configure your environment variables (see Environment Variables section)

  3. Start the development server:

    npm run dev

The Vite dev server starts at http://localhost:3001.

Next.js SSR Sandbox

A second sandbox lives in dev/nextjs/ and runs the hub inside a Next.js 15 + React 19 App Router app. Use it to verify server-side rendering behaviour.

From the repo root:

# First time (builds the hub and installs sandbox deps)
npm run dev:nextjs:setup
# Subsequent runs
npm run dev:nextjs

The Next.js sandbox runs at http://localhost:3002. Set STACKONE_API_KEY in dev/nextjs/.env (see dev/nextjs/.env.example) to have the page fetch a connect-session token server-side, or paste one into the input on the page.

After editing hub source, rerun npm run build from the repo root — the sandbox is linked via file:../.. so it picks up the new dist/ automatically.

🏗️ Build

To build the project for production:

npm run build

Build Output

The build generates multiple bundles in the dist/ directory:

FileDescriptionUse Case
dist/index.esm.jsES module bundle (with 'use client' banner)Modern React apps, Next.js, Vite
dist/index.jsCommonJS module (with 'use client' banner)Node.js / legacy environments
dist/index.d.tsTypeScript declarationsType-checking
dist/webcomponent.jsWeb component bundle (IIFE, React inlined)Vanilla HTML/JS integration

📖 Usage

🌐 Web Component Integration

@stackone/hub also ships a framework-agnostic custom element (<stackone-hub>) that works in plain HTML, Vue, Angular, Svelte, or any other framework. React + ReactDOM are bundled into the web-component bundle, so consumers do not need React installed.

Loading the bundle

Install the package and import the web-component subpath once at app startup. The import is side-effecting — it registers <stackone-hub> on customElements:

// main.ts / main.js — anywhere that runs once on app bootimport'@stackone/hub/webcomponent';

If you'd rather drop a <script> tag in plain HTML, point it at the IIFE bundle in node_modules (or your own static host):

<scriptsrc="/node_modules/@stackone/hub/dist/webcomponent.js"></script><stackone-hubtoken="..."></stackone-hub>

Attributes

All scalar StackOneHub props are exposed as kebab-case HTML attributes:

AttributeTypeNotes
tokenstringConnect-session token (required to render the picker).
base-urlstringAPI base URL. Defaults to https://api.stackone.com.
app-urlstringDashboard URL. Defaults to https://app.stackone.com.
modestringCurrently only integration-picker.
heightstringCSS height (e.g. 600px). Defaults to 500px.
themelight | dark | JSONEither the keyword or a JSON-encoded PartialMalachiteTheme object.
account-idstringOptional account filter.
on-close-labelstringOverride the close-button label.
show-footer-linksboolean attrPresence = true, value "false" = false.
debugboolean attrEnables debug logging.

Events

Callbacks are dispatched as CustomEvents on the host element:

Eventevent.detail
success{ id: string; provider: string } — emitted when an account is connected.
closeundefined — emitted when the user closes the picker.

Both events are dispatched with bubbles: true and composed: true, so they bubble and cross shadow-DOM boundaries.

JS-only properties

For values that don't fit in HTML attributes, set them as JS properties on the element:

PropertyTypePurpose
el.onSuccess(account) => voidFunction alternative to the success event.
el.onClose() => voidFunction alternative to the close event.
el.themeObjectPartialMalachiteThemeStructured theme; overrides the theme attribute.

Vanilla HTML

<!DOCTYPE html><html><head><scriptsrc="/node_modules/@stackone/hub/dist/webcomponent.js"></script></head><body><stackone-hubid="hub" token="..." mode="integration-picker" height="600px"></stackone-hub><script>constel=document.getElementById('hub');el.addEventListener('success',(e)=>console.log('connected',e.detail));el.addEventListener('close',()=>console.log('closed'));</script></body></html>

Vue 3

Tell Vue that <stackone-hub> is a custom element so the compiler doesn't try to resolve it as a Vue component:

// main.tsimport{createApp}from'vue';importAppfrom'./App.vue';import'@stackone/hub/webcomponent';constapp=createApp(App);app.config.compilerOptions.isCustomElement=(tag)=>tag==='stackone-hub';app.mount('#app');
<!-- App.vue -->
<script setup lang="ts">import { ref } from'vue';const token =ref('...');const onSuccess = (event:CustomEvent<{ id:string; provider:string }>) => {console.log('connected', event.detail);};</script>
<template>
<stackone-hub:token="token"mode="integration-picker"@success="onSuccess"@close="() => {}" />
</template>

Angular

Add CUSTOM_ELEMENTS_SCHEMA to the module (or standalone component) that uses <stackone-hub> so Angular treats unknown tags as custom elements:

// app.module.tsimport{NgModule,CUSTOM_ELEMENTS_SCHEMA}from'@angular/core';import'@stackone/hub/webcomponent';
@NgModule({declarations: [AppComponent],schemas: [CUSTOM_ELEMENTS_SCHEMA],bootstrap: [AppComponent],})exportclassAppModule{}
<!-- app.component.html --><stackone-hub[attr.token]="token"
mode="integration-picker"
(success)="onSuccess($event)"
(close)="onClose()"
></stackone-hub>
// app.component.tsonSuccess(event: Event){const{ id, provider }=(eventasCustomEvent<{id: string;provider: string}>).detail;console.log('connected',id,provider);}onClose(){console.log('closed');}

⚛️ React Component Integration

For React applications (CSR — Vite, CRA, etc.):

import{StackOneHub}from"@stackone/hub";functionApp(){return(<divclassName="app"><h1>My Application</h1><StackOneHubtoken={token}/></div>);}exportdefaultApp;

StackOneHub is a client-side component — it ships with a 'use client' directive and is safe to import directly in any framework that supports server-side rendering.

▲ Next.js (App Router) Integration

StackOneHub is annotated with 'use client' so you can import it directly from any Server Component. The token can be created server-side (recommended — keeps your API key off the client and avoids the CORS-protected /connect_sessions endpoint), and passed as a prop to a small Client Component that renders the hub.

Important: Add suppressHydrationWarning to the <html> tag in your root layout. The hub applies its theme CSS custom properties to document.documentElement after hydration, which would otherwise trigger a hydration warning on the <html> element (the warning only suppresses the <html> tag itself, not its children):

// app/layout.tsxexportdefaultfunctionRootLayout({ children }){return(<htmllang="en"suppressHydrationWarning><body>{children}</body></html>);}

app/page.tsx (Server Component):

importHubWrapperfrom"./HubWrapper";exportdefaultasyncfunctionPage(){constres=awaitfetch("https://api.stackone.com/connect_sessions",{method: "POST",headers: {"Content-Type": "application/json",Authorization: `Basic ${Buffer.from(process.env.STACKONE_API_KEY!).toString("base64")}`,},body: JSON.stringify({origin_owner_id: "your_customer_id",origin_owner_name: "Your Customer",origin_username: "your_username",}),cache: "no-store",});const{ token }=awaitres.json();return<HubWrappertoken={token}/>;}

app/HubWrapper.tsx (Client Component):

"use client";import{StackOneHub}from"@stackone/hub";exportdefaultfunctionHubWrapper({ token }: {token: string}){return(<StackOneHubtoken={token}mode="integration-picker"onSuccess={(account)=>console.log("connected",account)}/>);}

If you prefer to opt the hub out of SSR entirely (Pages Router, or to skip the server pre-render):

importdynamicfrom"next/dynamic";constStackOneHub=dynamic(()=>import("@stackone/hub").then((m)=>m.StackOneHub),{ssr: false},);

A working example lives in dev/nextjs/.

⚠️ "Invalid hook call" — duplicate React

@stackone/hub declares react and react-dom as peer dependencies and the bundle imports them at runtime — your app's copy must be the only copy that ends up loaded. In a standard npm install your bundler will hoist React and you won't see this. But the following setups can leave you with two copies of React and trip the "Invalid hook call" error:

  • Monorepos (npm workspaces, Yarn workspaces, Turborepo) where multiple packages each have their own node_modules/react.
  • pnpm with strict isolation — a transitive copy can shadow the root copy.
  • file: / link: dependencies pointing at a directory that has its own node_modules/react (this is what bit our local Vite sandbox).

Fixes by bundler:

Vite — add resolve.dedupe to your config:

// vite.config.tsexportdefaultdefineConfig({resolve: {dedupe: ['react','react-dom','react-hook-form'],},});

Webpack / Next.js — usually handled automatically. If not, alias react and react-dom to a single absolute path:

// next.config.mjsimportpathfrom"node:path";exportdefault{webpack: (config)=>{config.resolve.alias["react"]=path.resolve("./node_modules/react");config.resolve.alias["react-dom"]=path.resolve("./node_modules/react-dom");returnconfig;},};

pnpm — set public-hoist-pattern[]=react* in .npmrc, or shamefully-hoist=true.

To diagnose, run npm ls react (or pnpm why react) at your app's root — if you see more than one entry resolved to a different path, that's the cause.

💻 Local Development Usage

Web Component (Local)

<scriptsrc="dist/webcomponent.js"></script><stackone-hubtoken="..."></stackone-hub>

React Component (Local)

import{StackOneHub}from"../dist/index.esm.js";functionApp(){return<StackOneHubtoken={token}/>;}

🔧 Environment Variables

Create a .env file in the dev directory with the following variables:

VariableDescriptionRequired
STACKONE_API_KEYYour StackOne API key
ORIGIN_OWNER_IDThe origin owner identifier
ORIGIN_OWNER_NAMEDisplay name for the owner
ORIGIN_USERNAMEUsername for authentication
API_URLBackend API endpoint URL
DASHBOARD_URLDashboard application URL

Example .env file:

STACKONE_API_KEY=your_api_key_here
ORIGIN_OWNER_ID=your_owner_id
ORIGIN_OWNER_NAME=Your Company Name
ORIGIN_USERNAME=your_username
API_URL=https://api.stackone.com
DASHBOARD_URL=https://dashboard.stackone.com

🤝 Contributing

We welcome contributions and feedback! Please keep in mind:

  • 📋 No formal process yet: Contribution guidelines are still being established
  • 💬 Communication is key: Please open an issue before submitting large changes

Getting Started

  1. Fork the repository
  2. Create a feature branch
  3. Make your changes
  4. Test thoroughly
  5. Submit a pull request

📄 License

This project is licensed under the MIT License. See the LICENSE file for details.


About

Embeddable Integration Hub components

Resources

Stars

1 star

Watchers

3 watching

Forks

Releases

Packages

Used by

Contributors

Languages