@teamvortexsoftware/vortex-react-native
v1.0.3
Published
Vortex React Native SDK
Downloads
658
Readme
Vortex React Native SDK
Configuration-driven invitation forms for React Native apps.
Overview
The Vortex React Native SDK provides a React Native component for rendering customizable invitation forms in your React Native applications. The SDK fetches configuration from the Vortex platform and dynamically renders the appropriate UI.
Requirements
- React 18+
- React Native 0.75.0+
Installation
# Using pnpm (recommended)
pnpm add @teamvortexsoftware/vortex-react-native
# Using npm
npm install @teamvortexsoftware/vortex-react-native
# Using yarn
yarn add @teamvortexsoftware/vortex-react-nativeQuick Start
Basic Usage
import { VortexInvite } from '@teamvortexsoftware/vortex-react-native';
import { useState, useEffect } from 'react';
function MyInviteScreen() {
const [jwt, setJwt] = useState<string | null>(null);
useEffect(() => {
// Generate JWT for authenticated user
const generateJwt = async () => {
try {
const response = await fetch('https://your-api.com/api/vortex/jwt', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
});
const data = await response.json();
setJwt(data.jwt);
} catch (error) {
console.error('Failed to generate JWT:', error);
}
};
generateJwt();
}, []);
if (!jwt) {
return <Text>Loading...</Text>;
}
return (
<VortexInvite
componentId="my-component-id"
jwt={jwt}
scope="team-123"
scopeType="team"
onInvite={(data) => {
console.log('Invitation sent:', data);
}}
onError={(error) => {
console.error('Invitation error:', error);
}}
/>
);
}With Group Context
import { VortexInvite } from '@teamvortexsoftware/vortex-react-native';
function TeamInviteScreen({ teamId, teamName }: { teamId: string; teamName: string }) {
const { jwt, error } = useVortexAuth();
if (error) {
return <Text>Error: {error.message}</Text>;
}
return (
<VortexInvite
componentId={process.env.EXPO_PUBLIC_COMPONENT_ID}
jwt={jwt || ''}
scope={teamId}
scopeType="team"
onInvite={(result) => {
console.log('Invitations sent:', result);
}}
/>
);
}Complete Example with Modal
import { useState, useEffect } from 'react';
import { Modal, View, Button, StyleSheet } from 'react-native';
import { VortexInvite } from '@teamvortexsoftware/vortex-react-native';
function InviteModal({
visible,
onClose,
workspaceId,
}: {
visible: boolean;
onClose: () => void;
workspaceId: string;
}) {
const [jwt, setJwt] = useState<string>('');
useEffect(() => {
if (visible) {
// Generate JWT when modal opens
fetchJwt().then(setJwt);
}
}, [visible]);
return (
<Modal visible={visible} animationType="slide">
<View style={styles.container}>
<View style={styles.header}>
<Button title="Close" onPress={onClose} />
</View>
<VortexInvite
componentId={process.env.EXPO_PUBLIC_COMPONENT_ID}
jwt={jwt}
scope={workspaceId}
scopeType="workspace"
onInvite={(result) => {
console.log('Invitations sent successfully');
onClose();
}}
onError={(error) => {
console.error('Failed to send invitations:', error);
}}
/>
</View>
</Modal>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
},
header: {
padding: 16,
borderBottomWidth: 1,
borderBottomColor: '#e0e0e0',
},
});Invite Contacts
The Invite Contacts component displays a list of contacts that can be invited via SMS. Unlike the Contact Import feature (which fetches contacts from the device), Invite Contacts receives a pre-populated list of contacts from your app.
Basic Usage:
import { VortexInvite, InviteContactsConfig, InviteContactsContact } from '@teamvortexsoftware/vortex-react-native';
const contacts: InviteContactsContact[] = [
{ name: "Alice Johnson", phoneNumber: "+1 (555) 123-4567" },
{ name: "Bob Smith", phoneNumber: "+1 (555) 234-5678" },
{ name: "Carol Davis", phoneNumber: "+1 (555) 345-6789" }
];
<VortexInvite
componentId="your-component-id"
jwt={jwt}
inviteContactsConfig={{ contacts }}
onInvite={(result) => {
console.log('Invitations sent:', result);
}}
/>How It Works:
- The component shows an "Invite your contacts" entry with a contact count
- Tapping it navigates to a searchable list of contacts
- Each contact has an "Invite" button that:
- Creates an SMS invitation via the Vortex API
- Opens the device's SMS app (on supported devices)
- Pre-fills the message with the invitation link
InviteContactsContact Properties:
// Simple usage - just name and phone number (ID is auto-generated)
{
name: string; // Display name
phoneNumber: string; // Phone number for SMS
avatarUrl?: string; // Optional avatar image URL
metadata?: Record<string, any>; // Optional custom metadata
}
// Full usage - with custom ID
{
id: string; // Custom unique identifier
name: string; // Display name
phoneNumber: string; // Phone number for SMS
avatarUrl?: string; // Optional avatar image URL
metadata?: Record<string, any>; // Optional custom metadata
}InviteContactsConfig Properties:
{
contacts: InviteContactsContact[]; // List of contacts to display
}Find Friends
The Find Friends component displays a list of contacts provided by your app. Each contact has a "Connect" button that creates an invitation with targetType: internalId.
Basic Usage:
import { VortexInvite, FindFriendsConfig, FindFriendsContact } from '@teamvortexsoftware/vortex-react-native';
<VortexInvite
componentId="your-component-id"
jwt={jwt}
findFriendsConfig={{
contacts: [
{ internalId: "user-123", name: "Alice Johnson", subtitle: "@alice" },
{ internalId: "user-456", name: "Bob Smith", subtitle: "@bob" }
],
onInvitationCreated: (contact) => {
// Called after an invitation is successfully created
console.log('Invitation created for', contact.name);
}
}}
/>How It Works:
- Your app provides a list of contacts with internal IDs (users already in your platform)
- The component displays them with a "Connect" button (text configurable via widget config)
- When the user taps "Connect", the SDK creates an invitation via the Vortex API with
targetType: internalId - The
onInvitationCreatedcallback is called after a successful invitation - The section is hidden when there are no contacts to display
FindFriendsContact Properties:
{
internalId: string; // Required: ID in your platform
name: string; // Required: Display name
subtitle?: string; // Optional: Secondary text (e.g., username)
avatarUrl?: string; // Optional: Avatar image URL
metadata?: Record<string, any>; // Optional: Custom metadata
}FindFriendsConfig Properties:
{
contacts: FindFriendsContact[]; // Required: List of contacts to display
onInvitationCreated?: (contact: FindFriendsContact) => void; // Optional: Called after successful invitation
}Invitation Suggestions
The Invitation Suggestions component displays a list of suggested contacts provided by your app. Each contact has an "Invite" button and a dismiss (X) button. When the user taps "Invite", an invitation with targetType: internalId is created. The dismiss button removes the suggestion from the list.
Basic Usage:
import { VortexInvite, InvitationSuggestionsConfig, InvitationSuggestionContact } from '@teamvortexsoftware/vortex-react-native';
<VortexInvite
componentId="your-component-id"
jwt={jwt}
invitationSuggestionsConfig={{
contacts: [
{
internalId: "user-123",
name: "Alice Johnson",
subtitle: "@alice",
avatarUrl: "https://example.com/alice-avatar.jpg"
},
{
internalId: "user-456",
name: "Bob Smith",
subtitle: "@bob"
}
],
onDismiss: (contact) => {
// Called when user taps the X button
console.log('Dismissed suggestion for', contact.name);
},
onInvitationCreated: (contact) => {
console.log('Invitation created for', contact.name);
},
onInvitationFailed: (contact, error) => {
console.error('Failed to invite', contact.name, error);
}
}}
/>How It Works:
- Your app provides a list of suggested contacts with internal IDs
- The component displays them with an "Invite" button and a dismiss (X) button
- When the user taps "Invite", the SDK creates an invitation via the Vortex API with
targetType: internalId - The
onInvitationCreatedoronInvitationFailedcallback is called based on the result - When the user taps the X button, the
onDismisscallback is called and the contact is removed from the list
InvitationSuggestionContact Properties:
{
internalId: string; // Required: ID in your platform
name: string; // Required: Display name
subtitle?: string; // Optional: Secondary text (e.g., username)
avatarUrl?: string; // Optional: Avatar image URL (rendered instead of initials if provided)
metadata?: Record<string, any>; // Optional: Custom metadata
}InvitationSuggestionsConfig Properties:
{
contacts: InvitationSuggestionContact[]; // Required: List of contacts to display
onDismiss: (contact: InvitationSuggestionContact) => void; // Required: Called when user dismisses a suggestion
onInvitationCreated?: (contact: InvitationSuggestionContact) => void; // Called after successful invitation
onInvitationFailed?: (contact: InvitationSuggestionContact, error: Error) => void; // Called on failure
}Search Box
The Search Box component displays a search input with a search button. When the user taps the search button, your app's onSearch callback is invoked to return matching contacts. The results are rendered below the search box with "Connect" buttons, identical to the Find Friends component.
Basic Usage:
import { VortexInvite, SearchBoxConfig, SearchBoxContact } from '@teamvortexsoftware/vortex-react-native';
<VortexInvite
componentId="your-component-id"
jwt={jwt}
searchBoxConfig={{
onSearch: async (query) => {
// Return matching contacts from your backend or local data
const results = await myAPI.searchUsers(query);
return results.map(user => ({
internalId: user.id,
name: user.displayName,
subtitle: `@${user.username}`,
avatarUrl: user.avatarUrl
}));
},
onInvitationCreated: (contact) => {
console.log('Invitation created for', contact.name);
}
}}
/>How It Works:
- The component renders a search text field with a configurable placeholder and a search button
- An optional title can be displayed above the search box (configured in the widget editor)
- When the user taps the search button, your
onSearchcallback is called with the query string - Your callback returns a list of
SearchBoxContactobjects - The matching contacts are rendered below the search box with a "Connect" button next to each
- If the search returns no results, a configurable "no results" message is displayed
- When the user taps "Connect", the SDK creates an invitation via the Vortex API with
targetType: internalIdand the contact is removed from the list (identical to Find Friends behavior)
SearchBoxConfig Properties:
{
onSearch: (query: string) => Promise<SearchBoxContact[]>; // Required: Search callback
onInvitationCreated?: (contact: SearchBoxContact) => void; // Optional: Called after successful invitation
}Note: The placeholder text, connect button text, no-results message, and title are all configurable from the widget editor.
Incoming Invitations
The Incoming Invitations component displays invitations the user has received, with Accept and Delete actions.
Basic Usage:
import { VortexInvite, IncomingInvitationsConfig } from '@teamvortexsoftware/vortex-react-native';
<VortexInvite
componentId="your-component-id"
jwt={jwt}
incomingInvitationsConfig={{
onAccept: async (invitation) => {
// Handle acceptance (return true to proceed with API call)
await myAPI.acceptInvitation(invitation.id);
return true;
},
onDelete: async (invitation) => {
// Handle deletion (return true to proceed with API call)
return true;
}
}}
/>With Internal Invitations:
You can merge your app's invitations with Vortex API invitations:
<VortexInvite
componentId="your-component-id"
jwt={jwt}
incomingInvitationsConfig={{
internalInvitations: [
{
id: "internal-1",
name: "Alice Johnson",
subtitle: "[email protected]",
avatarUrl: "https://example.com/avatar.jpg"
}
],
onAccept: async (invitation) => {
if (invitation.isVortexInvitation) {
// Vortex invitation: return true to let SDK call the Vortex API
return true;
} else {
// Internal/app invitation: handle it yourself
await myAPI.acceptInvitation(invitation.id);
return true; // Return true to remove from list
}
},
onDelete: async (invitation) => {
if (invitation.isVortexInvitation) {
return true; // Let SDK handle the API call
} else {
await myAPI.deleteInvitation(invitation.id);
return true; // Return true to remove from list
}
}
}}
/>Identifying Invitation Source:
Use the isVortexInvitation property to determine where an invitation came from:
true: Fetched from the Vortex API — the SDK will handle accept/delete API callsfalse: Provided by your app viainternalInvitations— your app must handle the action
IncomingInvitationsConfig Properties:
{
internalInvitations?: IncomingInvitationItem[]; // App-provided invitations (isVortexInvitation = false)
onAccept?: (invitation: IncomingInvitationItem) => Promise<boolean>; // Called when user accepts
onDelete?: (invitation: IncomingInvitationItem) => Promise<boolean>; // Called when user deletes
}Callback Return Values:
| Invitation Source | Return true | Return false |
|-------------------|---------------|----------------|
| Vortex (isVortexInvitation === true) | SDK calls Vortex API, removes from list | Cancels the action |
| Internal (isVortexInvitation === false) | Removes from list (no API call) | Keeps in list |
Outgoing Invitations
The Outgoing Invitations component displays invitations the user has sent, with a Cancel action.
Basic Usage:
import { VortexInvite, OutgoingInvitationsConfig } from '@teamvortexsoftware/vortex-react-native';
<VortexInvite
componentId="your-component-id"
jwt={jwt}
outgoingInvitationsConfig={{
onCancel: async (invitation) => {
// Handle cancellation (return true to proceed with API call)
return true;
}
}}
/>With Internal Invitations:
You can merge your app's outgoing invitations with Vortex API invitations:
<VortexInvite
componentId="your-component-id"
jwt={jwt}
outgoingInvitationsConfig={{
internalInvitations: [
{
id: "internal-1",
name: "Bob Smith",
subtitle: "user-12345", // e.g., user ID or other identifier
avatarUrl: "https://example.com/avatar.jpg"
}
],
onCancel: async (invitation) => {
if (invitation.isVortexInvitation) {
// Vortex invitation: return true to let SDK call the Vortex API
return true;
} else {
// Internal/app invitation: handle it yourself
await myAPI.cancelInvitation(invitation.id);
return true; // Return true to remove from list
}
}
}}
/>Identifying Invitation Source:
Use the isVortexInvitation property to determine where an invitation came from:
true: Fetched from the Vortex API — the SDK will handle cancel API callsfalse: Provided by your app viainternalInvitations— your app must handle the action
OutgoingInvitationsConfig Properties:
{
internalInvitations?: OutgoingInvitationItem[]; // App-provided invitations (isVortexInvitation = false)
onCancel?: (invitation: OutgoingInvitationItem) => Promise<boolean>; // Called when user cancels
}Callback Return Values:
| Invitation Source | Return true | Return false |
|-------------------|---------------|----------------|
| Vortex (isVortexInvitation === true) | SDK calls Vortex API, removes from list | Cancels the action |
| Internal (isVortexInvitation === false) | Removes from list (no API call) | Keeps in list |
Prefetch for Instant Rendering
The SDK supports prefetching widget configurations to eliminate loading delays when opening the invite form. This uses a stale-while-revalidate pattern: cached configurations are shown immediately while fresh data is fetched in the background.
Basic Prefetch Example
import { VortexInvite, usePrefetchWidgetConfiguration } from '@teamvortexsoftware/vortex-react-native';
import { useState, useEffect } from 'react';
function InviteModal({ onClose, groupId }) {
const [jwt, setJwt] = useState<string | undefined>();
// Fetch JWT on mount
useEffect(() => {
fetchJwt().then(setJwt);
}, []);
// Prefetch widget configuration as soon as JWT is available
const { widgetConfiguration } = usePrefetchWidgetConfiguration({
componentId: process.env.EXPO_PUBLIC_COMPONENT_ID,
vortexApiUrl: 'https://client-api.vortexsoftware.com',
jwt,
enabled: !!jwt, // Only prefetch when JWT is ready
});
return (
<VortexInvite
componentId={process.env.EXPO_PUBLIC_COMPONENT_ID}
jwt={jwt}
widgetConfiguration={widgetConfiguration} // Pass prefetched config
scope={groupId}
scopeType="team"
onClose={onClose}
/>
);
}How It Works
Without Prefetching (default behavior):
- Component mounts → Shows loading spinner
- Fetches configuration → Renders form
With Prefetching (optimized):
- Parent prefetches configuration early (when JWT becomes available)
- Component mounts → Renders form immediately (no loading spinner!)
- Fetches latest configuration in background
- Updates form if configuration changed
Benefits
✅ Instant rendering - No loading spinner when modal/screen is shown
✅ Always fresh - Component still fetches latest configuration in background
✅ Automatic updates - Form updates if configuration changes on server
✅ Backward compatible - Works perfectly without prefetching (shows loading spinner as before)
Optional Features
The SDK supports optional native features that enhance the user experience. These features require additional libraries that you can install based on your needs.
Why Optional?
These features use native code that must be explicitly installed in your app. The SDK gracefully degrades when these libraries aren't installed:
- Gradients → Falls back to solid colors
- Haptics → Silently skipped
- QR Codes → Shows placeholder message
- Clipboard → Falls back to web clipboard API, or shows error with install instructions
- Contacts → Shows error with install instructions when contacts import is triggered
- Google Sign-In → Shows warning when Google contacts import is triggered
Configuration
Specify which libraries you've installed via the unified modules prop on VortexInvite:
<VortexInvite
componentId="your-component-id"
jwt={jwt}
// Optional feature modules (unified configuration)
modules={{
gradient: 'expo-linear-gradient',
haptics: 'expo-haptics',
qrCode: 'react-native-qrcode-svg',
clipboard: 'expo-clipboard',
contacts: 'expo-contacts',
googleSignin: '@react-native-google-signin/google-signin',
}}
/>Available Features
| Feature | Property | Options | Description |
|---------|----------|---------|-------------|
| Gradients | modules.gradient | 'expo-linear-gradient' or 'react-native-linear-gradient' | Gradient backgrounds on buttons |
| Haptics | modules.haptics | 'expo-haptics' | Haptic feedback on button presses |
| QR Codes | modules.qrCode | 'react-native-qrcode-svg' or 'react-qr-code' | QR code display for shareable links |
| Clipboard | modules.clipboard | 'expo-clipboard' or '@react-native-clipboard/clipboard' | Clipboard operations (copy link) |
| Contacts | modules.contacts | 'expo-contacts' | Device contacts import |
| Google Sign-In | modules.googleSignin | '@react-native-google-signin/google-signin' | Google Contacts import |
| Sharing | modules.sharing | 'expo-sharing' | Native share sheet |
Installation by Project Type
Expo Projects (Recommended)
# Gradients
npx expo install expo-linear-gradient
# Haptics
npx expo install expo-haptics
# QR Codes (requires both packages)
npx expo install react-native-qrcode-svg react-native-svg
# Clipboard
npx expo install expo-clipboard
# Contacts (device contacts import)
npx expo install expo-contacts
# Google Sign-In (Google contacts import)
npm install @react-native-google-signin/google-signin
# Sharing
npx expo install expo-sharingThen configure:
<VortexInvite
componentId="your-component-id"
jwt={jwt}
modules={{
gradient: 'expo-linear-gradient',
haptics: 'expo-haptics',
qrCode: 'react-native-qrcode-svg',
clipboard: 'expo-clipboard',
contacts: 'expo-contacts',
googleSignin: '@react-native-google-signin/google-signin',
sharing: 'expo-sharing',
}}
/>Bare React Native Projects
# Gradients
npm install react-native-linear-gradient
cd ios && pod install
# QR Codes (requires both packages)
npm install react-native-qrcode-svg react-native-svg
cd ios && pod install
# Clipboard
npm install @react-native-clipboard/clipboard
cd ios && pod install
# Contacts (device contacts import)
npx expo install expo-contacts
# or for bare RN, follow expo-contacts installation docs
# Google Sign-In (Google contacts import)
npm install @react-native-google-signin/google-signin
cd ios && pod installThen configure:
<VortexInvite
componentId="your-component-id"
jwt={jwt}
modules={{
gradient: 'react-native-linear-gradient',
qrCode: 'react-native-qrcode-svg',
clipboard: '@react-native-clipboard/clipboard',
contacts: 'expo-contacts',
googleSignin: '@react-native-google-signin/google-signin',
}}
/>Helpful Warnings
In development mode, the SDK will print helpful warnings when:
- A feature is detected in your widget configuration but no module is specified
- A module is specified but not installed
Example warning:
[Vortex] ⚠️ Gradient backgrounds detected in widget configuration, but no gradient module specified.
To enable gradients on native platforms, add the modules prop to <VortexInvite>:
For Expo projects:
<VortexInvite modules={{ gradient: 'expo-linear-gradient' }} ... />
Then run: npx expo install expo-linear-gradientDeferred Deep Linking
Deferred deep linking allows your app to retrieve invitation context even when a user installs the app after clicking an invitation link. When a user clicks an invitation link but doesn't have the app installed, they're redirected to the App Store or Play Store. After installation, the SDK can match the device fingerprint to retrieve the original invitation context.
Basic Usage
Call VortexDeferredLinks.retrieveDeferredDeepLink when the user signs in or when the app session is restored:
import { VortexDeferredLinks } from '@teamvortexsoftware/vortex-react-native';
// In your authentication manager or app initialization
async function onUserSignedIn(vortexJwt: string) {
try {
const result = await VortexDeferredLinks.retrieveDeferredDeepLink({ jwt: vortexJwt });
if (result.matched && result.deepLink) {
console.log('Found pending invitation!');
console.log('Invitation ID:', result.invitationId);
console.log('Deep Link:', result.deepLink);
console.log('Metadata:', result.metadata);
// Handle the invitation (e.g., show UI, auto-join group, navigate to specific screen)
}
} catch (error) {
console.error('Deferred link check failed:', error);
}
}Response Types
MatchFingerprintResponse:
interface MatchFingerprintResponse {
matched: boolean; // Whether a matching invitation was found
invitationId?: string; // The original invitation ID
deepLink?: string; // Deep link URL to handle
metadata?: Record<string, any>; // Additional metadata
}Best Practices
- Call on authentication: Check for deferred deep links immediately after user sign-in or session restore
- Use Vortex JWT: The endpoint requires a Vortex JWT token (not your app's auth token)
- Handle once: Clear or mark the invitation as handled after processing to avoid showing it repeatedly
- Graceful degradation: The check may fail (network issues, no match found) - handle errors gracefully
Example Integration with Navigation
import { useEffect } from 'react';
import { VortexDeferredLinks } from '@teamvortexsoftware/vortex-react-native';
function App() {
const [jwt, setJwt] = useState<string | null>(null);
const navigation = useNavigation();
useEffect(() => {
if (jwt) {
checkForPendingInvitations();
}
}, [jwt]);
async function checkForPendingInvitations() {
try {
const result = await VortexDeferredLinks.retrieveDeferredDeepLink({ jwt });
if (result.matched && result.deepLink) {
// Parse deep link and navigate
const params = parseDeepLink(result.deepLink);
navigation.navigate('TeamScreen', { teamId: params.teamId });
}
} catch (error) {
// Log error but don't block the user
console.error('Deferred deep link check failed:', error);
}
}
return <YourApp />;
}Unfurl Configuration
When invitation links are shared on social platforms (iMessage, WhatsApp, Facebook, Twitter, etc.), the UnfurlConfig allows you to customize the Open Graph metadata that appears in the link preview card.
Usage
<VortexInvite
componentId="your-component-id"
jwt={jwt}
scope={teamId}
scopeType="team"
unfurlConfig={{
title: "Join our team!",
description: "You've been invited to collaborate on an exciting project",
image: "https://example.com/preview-image.png",
siteName: "My App",
type: "website"
}}
onInvite={(result) => {
console.log('Invitations sent:', result);
}}
/>UnfurlConfig Properties
| Property | Type | Description |
|----------|------|-------------|
| title | string? | The title shown in the link preview (og:title) |
| description | string? | The description shown in the link preview (og:description) |
| image | string? | URL to the image shown in the link preview (og:image). Must be a valid URL. |
| siteName | string? | The site name shown in the link preview (og:site_name) |
| type | string? | The Open Graph type (og:type). Defaults to "website" if not provided. |
Priority Chain
The backend uses this priority for unfurl values:
- Invitation metadata (values from
UnfurlConfig) - highest priority - Domain profile unfurlData - fallback from account settings in Vortex dashboard
- Hardcoded defaults - last resort
Features
Invitation Methods:
- Email invitations with validation
- Copy shareable link to clipboard
- Native share sheet integration
- SMS sharing
- QR code generation
- WhatsApp, LINE, and other social messaging integrations
Contact Import:
- Device Contacts integration (iOS/Android)
- Google Contacts integration (via Google Sign-In)
Advanced Components:
- Invite Contacts - Display a list of contacts for SMS invitation
- Find Friends - Display contacts with "Connect" buttons for internal invitations
- Invitation Suggestions - Show suggested contacts with invite/dismiss actions
- Search Box - Search and invite users from your platform
- Incoming Invitations - Display and manage received invitations
- Outgoing Invitations - Display and manage sent invitations
Core Capabilities:
- Dynamic form rendering from server configuration
- JWT authentication
- Group/team/workspace context support
- Real-time loading states and error handling
- Customizable UI based on widget configuration
- Deferred deep linking via fingerprint matching
- Prefetching for instant rendering
- Open Graph unfurl metadata customization
Optional Native Features:
- Linear gradients (Expo or React Native)
- Haptic feedback (Expo)
- QR code generation (react-native-qrcode-svg or react-qr-code)
- Enhanced clipboard (Expo or React Native Community)
- Device contacts import (Expo)
- Google Contacts import (via Google Sign-In)
- Native share sheet (Expo)
API Reference
VortexInvite
The main React Native component for rendering invitation forms.
Props:
| Prop | Type | Required | Description |
|------|------|----------|-------------|
| componentId | string | ✅ | Unique identifier for the Vortex component |
| jwt | string | ✅ | JWT token for authenticated user |
| scope | string | ✅ | Scope identifier (e.g., team ID, workspace ID) |
| scopeType | string | ✅ | Type of scope (e.g., "team", "workspace", "project") |
| widgetConfiguration | WidgetConfiguration | ❌ | Pre-fetched widget configuration |
| onInvite | (result: any) => void | ❌ | Callback when invitations are successfully sent |
| onError | (error: Error) => void | ❌ | Callback when an error occurs |
| onClose | () => void | ❌ | Callback when the component is closed |
| templateVariables | Record<string, string> | ❌ | Variables for invitation templates |
| analyticsSegmentation | Record<string, any> | ❌ | Analytics tracking data |
| modules | VortexModules | ❌ | Unified configuration for optional native libraries (see below) |
| inviteContactsConfig | InviteContactsConfig | ❌ | Configuration for Invite Contacts feature |
| findFriendsConfig | FindFriendsConfig | ❌ | Configuration for Find Friends feature |
| invitationSuggestionsConfig | InvitationSuggestionsConfig | ❌ | Configuration for Invitation Suggestions feature |
| searchBoxConfig | SearchBoxConfig | ❌ | Configuration for Search Box feature |
| incomingInvitationsConfig | IncomingInvitationsConfig | ❌ | Configuration for Incoming Invitations feature |
| outgoingInvitationsConfig | OutgoingInvitationsConfig | ❌ | Configuration for Outgoing Invitations feature |
| unfurlConfig | UnfurlConfig | ❌ | Configuration for Open Graph unfurl metadata |
modules Object Properties:
| Property | Type | Description |
|----------|------|-------------|
| gradient | 'expo-linear-gradient' | 'react-native-linear-gradient' | Gradient library for button backgrounds |
| haptics | 'expo-haptics' | Haptics library for touch feedback |
| qrCode | 'react-native-qrcode-svg' | 'react-qr-code' | QR code library |
| clipboard | 'expo-clipboard' | '@react-native-clipboard/clipboard' | Clipboard library |
| contacts | 'expo-contacts' | Device contacts import |
| googleSignin | '@react-native-google-signin/google-signin' | Google Contacts import |
| sharing | 'expo-sharing' | Sharing library |
usePrefetchWidgetConfiguration
Hook for prefetching widget configuration.
Options:
| Option | Type | Required | Description |
|--------|------|----------|-------------|
| componentId | string | ✅ | Your widget component ID |
| vortexApiUrl | string | ✅ | Vortex API URL |
| jwt | string | ❌ | JWT token (or use user) |
| user | string \| object | ❌ | User data for unsigned JWT |
| enabled | boolean | ❌ | Conditionally enable/disable prefetching (default: true) |
Return Value:
| Property | Type | Description |
|----------|------|-------------|
| widgetConfiguration | WidgetConfiguration \| undefined | The prefetched configuration |
| isLoading | boolean | True while fetching |
| error | Error \| null | Error if fetch failed |
VortexDeferredLinks
Class for handling deferred deep linking - track invitation attribution even when the app wasn't installed at the time the invitation link was clicked.
Methods:
| Method | Parameters | Returns | Description |
|--------|------------|---------|-------------|
| collectDeviceFingerprint() | none | DeviceFingerprint | Collects device fingerprint data for matching |
| retrieveDeferredDeepLink(options) | RetrieveDeferredDeepLinkOptions | Promise<MatchFingerprintResponse> | Retrieves deferred deep link by matching fingerprint |
Example:
import { VortexDeferredLinks } from '@teamvortexsoftware/vortex-react-native';
// On app first launch after install
async function checkDeferredDeepLink(jwt: string) {
const result = await VortexDeferredLinks.retrieveDeferredDeepLink({ jwt });
if (result.matched && result.deepLink) {
console.log('User came from invitation:', result.invitationId);
// Navigate to appropriate screen based on deepLink
}
}Types:
interface DeviceFingerprint {
platform: 'ios' | 'android';
osVersion: string;
deviceModel: string;
deviceBrand: string;
timezone: string;
language: string;
screenWidth: number;
screenHeight: number;
carrierName?: string;
totalMemory?: number;
}
interface MatchFingerprintResponse {
matched: boolean;
invitationId?: string;
deepLink?: string;
metadata?: Record<string, any>;
}
interface RetrieveDeferredDeepLinkOptions {
jwt: string;
}VortexClient
Standalone API client for invitation management. Use this when you need to interact with invitations outside of the VortexInvite component (e.g., in your own screens or background logic).
Constructor:
import { VortexClient } from '@teamvortexsoftware/vortex-react-native';
const client = new VortexClient({
jwt: 'your-jwt-token',
});Methods:
| Method | Parameters | Returns | Description |
|--------|------------|---------|-------------|
| setJwt(jwt) | jwt: string | void | Updates the JWT token used for authentication |
| getInvitation(invitationId) | invitationId: string | Promise<Invitation> | Retrieves details of a specific invitation by its ID |
| acceptInvitation(invitationId) | invitationId: string | Promise<void> | Accepts an incoming invitation |
| revokeInvitation(invitationId) | invitationId: string | Promise<void> | Revokes (deactivates) an invitation where the user is either the creator or a target |
Example — Get, Accept, and Revoke invitations:
import { VortexClient, Invitation } from '@teamvortexsoftware/vortex-react-native';
const client = new VortexClient({ jwt: userToken });
try {
// Get invitation details
const invitation: Invitation = await client.getInvitation('invitation-id');
console.log(invitation.status, invitation.target);
// Accept an incoming invitation
await client.acceptInvitation('invitation-id');
// Revoke (deactivate) an invitation
await client.revokeInvitation('invitation-id');
} catch (error) {
console.error('Operation failed:', error.message);
}Types:
interface Invitation {
id: string;
accountId: string;
projectId: string;
deploymentId: string;
widgetConfigurationId: string | null;
status: string;
invitationType: string;
deliveryTypes: string[];
source?: string | null;
subtype?: string | null;
foreignCreatorId: string | null;
creatorName?: string | null;
creatorAvatarUrl?: string | null;
createdAt: string;
modifiedAt: string | null;
deactivated: boolean;
deliveryCount: number;
views: number | null;
clickThroughs: number | null;
configurationAttributes: Record<string, any>;
attributes: Record<string, any> | null;
metadata: Record<string, any> | null;
passThrough?: string | null;
target: InvitationTarget[];
groups?: InvitationGroup[];
accepts?: InvitationAcceptance[];
scope?: string;
scopeType?: string;
expired: boolean;
expires?: string;
}
interface InvitationTarget {
type: string;
value: string;
name?: string | null;
avatarUrl?: string | null;
}
interface InvitationGroup {
id: string;
groupId: string;
type: string;
name?: string | null;
}
interface InvitationAcceptance {
id: string;
accountId: string;
projectId: string;
acceptedAt: string;
targetType: string;
targetValue: string;
identifiers?: Record<string, any> | null;
}Type Definitions
InviteContactsConfig:
interface InviteContactsConfig {
contacts: InviteContactsContact[];
}
interface InviteContactsContact {
id?: string;
name: string;
phoneNumber: string;
avatarUrl?: string;
metadata?: Record<string, any>;
}FindFriendsConfig:
interface FindFriendsConfig {
contacts: FindFriendsContact[];
onInvitationCreated?: (contact: FindFriendsContact) => void;
}
interface FindFriendsContact {
internalId: string;
name: string;
subtitle?: string;
avatarUrl?: string;
metadata?: Record<string, any>;
}InvitationSuggestionsConfig:
interface InvitationSuggestionsConfig {
contacts: InvitationSuggestionContact[];
onDismiss: (contact: InvitationSuggestionContact) => void;
onInvitationCreated?: (contact: InvitationSuggestionContact) => void;
onInvitationFailed?: (contact: InvitationSuggestionContact, error: Error) => void;
}
interface InvitationSuggestionContact {
internalId: string;
name: string;
subtitle?: string;
avatarUrl?: string;
metadata?: Record<string, any>;
}SearchBoxConfig:
interface SearchBoxConfig {
onSearch: (query: string) => Promise<SearchBoxContact[]>;
onInvitationCreated?: (contact: SearchBoxContact) => void;
}
interface SearchBoxContact {
internalId: string;
name: string;
subtitle?: string;
avatarUrl?: string;
metadata?: Record<string, any>;
}IncomingInvitationsConfig:
interface IncomingInvitationsConfig {
internalInvitations?: IncomingInvitationItem[];
onAccept?: (invitation: IncomingInvitationItem) => Promise<boolean>;
onDelete?: (invitation: IncomingInvitationItem) => Promise<boolean>;
}
interface IncomingInvitationItem {
id: string;
name: string;
subtitle?: string;
avatarUrl?: string;
isVortexInvitation: boolean;
metadata?: Record<string, any>;
}OutgoingInvitationsConfig:
interface OutgoingInvitationsConfig {
internalInvitations?: OutgoingInvitationItem[];
onCancel?: (invitation: OutgoingInvitationItem) => Promise<boolean>;
}
interface OutgoingInvitationItem {
id: string;
name: string;
subtitle?: string;
avatarUrl?: string;
isVortexInvitation: boolean;
metadata?: Record<string, any>;
}UnfurlConfig:
interface UnfurlConfig {
title?: string;
description?: string;
image?: string;
siteName?: string;
type?: string;
}Authentication
Your backend should generate JWTs for authenticated users:
Node.js Example
import { generateJwt } from '@teamvortexsoftware/vortex-node-22-sdk';
const jwt = generateJwt({
user: {
id: user.id,
email: user.email,
adminScopes: user.isAdmin ? ['autojoin'] : [],
},
apiKey: process.env.VORTEX_API_KEY,
});Express/Fastify Example
app.post('/api/vortex/jwt', async (req, res) => {
const user = await getCurrentUser(req);
const jwt = generateJwt({
user: {
id: user.id,
email: user.email,
adminScopes: user.isAutojoinAdmin ? ['autojoin'] : [],
},
apiKey: process.env.VORTEX_API_KEY,
});
res.json({ jwt });
});Configuration
The SDK automatically fetches and caches widget configuration from your Vortex backend. The configuration determines:
- Available invitation methods
- Form fields and validation
- UI theme and styling
- Enabled features
Environment Variables
Set up your environment variables in .env:
EXPO_PUBLIC_COMPONENT_ID=your-component-id-hereError Handling
The SDK provides error callbacks for handling failures:
<VortexInvite
componentId="my-component-id"
jwt={jwt}
scope={teamId}
scopeType="team"
onError={(error) => {
// Handle specific error types
if (error.message.includes('network')) {
Alert.alert('Network Error', 'Please check your connection and try again.');
} else if (error.message.includes('unauthorized')) {
Alert.alert('Authentication Error', 'Please log in again.');
} else {
Alert.alert('Error', error.message);
}
}}
/>Common error scenarios:
- Network errors - Connection issues or API unavailable
- Authentication errors - Invalid or expired JWT
- Configuration errors - Invalid component ID or missing configuration
- Validation errors - Invalid form input (e.g., malformed email)
Development
# Install dependencies
pnpm install
# Build the package
pnpm run build
# Run type checking
pnpm run type-check
# Run linting
pnpm run lint
# Watch mode for development
pnpm run devLicense
Apache License 2.0 - see LICENSE file for details.
Support
For questions or issues:
- 📚 Documentation: https://docs.vortexsoftware.com
- 📧 Email: [email protected]
- 🐛 GitHub Issues: https://github.com/teamvortexsoftware/vortex/issues
