JSPM

  • Created
  • Published
  • Downloads 9
  • Score
    100M100P100Q65850F
  • License MIT

A Firebase-powered Content Management System SDK optimized for kiosks with offline support and template management

Package Exports

  • @bigdigital/kiosk-content-sdk
  • @bigdigital/kiosk-content-sdk/dist/index.js

This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@bigdigital/kiosk-content-sdk) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.

Readme

npm install @bigdigital/kiosk-content-sdk


## Basic Usage

```typescript
import { useKioskContent } from '@bigdigital/kiosk-content-sdk';

function App() {
  const config = {
    projectId: process.env.VITE_FIREBASE_PROJECT_ID,
    apiKey: process.env.VITE_FIREBASE_API_KEY,
    offlineSupport: true, // Enable offline support
    cacheStrategy: "local", // Use local storage for caching
    syncInterval: 300000, // Sync every 5 minutes
    cacheMaxAge: 600000 // Cache expires after 10 minutes
  };

  const { content, loading, error, isSyncing, isOnline } = useKioskContent(config);

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      {isSyncing && <div>Syncing...</div>}
      {content.map(item => (
        <div key={item.id}>
          <h2>{item.title}</h2>
          <p>{item.description}</p>
        </div>
      ))}
    </div>
  );
}

Content Management

Retrieving Content by ID

Use useContent hook to fetch and manage specific content items:

import { useContent } from '@bigdigital/kiosk-content-sdk';

function ContentViewer({ contentId }: { contentId: string }) {
  const { content, loading, error, refresh } = useContent(contentId);

  if (loading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;
  if (!content) return <div>Content not found</div>;

  return (
    <div>
      <h1>{content.title}</h1>
      <p>{content.description}</p>
      {content.type === 'template' && (
        <TemplateContent 
          values={content.templateValues}
          templateId={content.templateId}
        />
      )}
      <button onClick={refresh}>Refresh Content</button>
    </div>
  );
}

Project-based Content Management

Filter and organize content by projects:

import { useProjectContent } from '@bigdigital/kiosk-content-sdk';

function ProjectContent({ projectId }: { projectId: string }) {
  const { 
    content,
    loading,
    error,
    filters,
    setFilters,
    pagination,
    setPagination
  } = useProjectContent(projectId, {
    initialFilters: {
      type: 'template',
      status: 'published',
      tags: ['featured']
    },
    initialPagination: {
      page: 1,
      limit: 10
    }
  });

  return (
    <div>
      {/* Filter Controls */}
      <div className="filters">
        <select 
          value={filters.type} 
          onChange={e => setFilters(prev => ({ ...prev, type: e.target.value }))}
        >
          <option value="all">All Types</option>
          <option value="template">Templates</option>
          <option value="text">Text</option>
          <option value="media">Media</option>
        </select>
      </div>

      {/* Content List */}
      {content.map(item => (
        <ContentCard key={item.id} content={item} />
      ))}

      {/* Pagination */}
      <div className="pagination">
        <button 
          onClick={() => setPagination(prev => ({ 
            ...prev, 
            page: prev.page - 1 
          }))}
          disabled={pagination.page === 1}
        >
          Previous
        </button>
        <span>Page {pagination.page}</span>
        <button 
          onClick={() => setPagination(prev => ({ 
            ...prev, 
            page: prev.page + 1 
          }))}
          disabled={content.length < pagination.limit}
        >
          Next
        </button>
      </div>
    </div>
  );
}

Templates and Content Values

Working with Template-based Content

Templates define the structure of your content using fields organized into logical groups:

// Template structure
const template = {
  id: "product-template",
  name: "Product Template",
  description: "Template for product content",
  type: "product",
  fields: {
    "field-1": {
      id: "field-1",
      name: "productName",
      label: "Product Name",
      type: "text",
      validation: { required: true }
    },
    "field-2": {
      id: "field-2",
      name: "price",
      label: "Product Price",
      type: "number",
      validation: { required: true, min: 0 }
    }
  },
  groups: {
    "group-1": {
      id: "group-1",
      name: "Basic Info",
      description: "Basic product information",
      order: 1,
      fieldIds: ["field-1", "field-2"]
    }
  },
  groupOrder: ["group-1"],
  ungroupedFieldIds: []
};

// Creating content with template values
const content = {
  title: "Premium Widget",
  description: "High-quality widget for professional use",
  type: "template",
  templateId: "product-template",
  templateValues: {
    groups: {
      basicInfo: { // Note: Group names are in camelCase
        productName: "Premium Widget Pro",
        price: 299.99
      }
    },
    ungrouped: {} // Values for fields not in any group
  }
};

Rendering Template Content

function TemplateContent({ contentId }: { contentId: string }) {
  const { content, template } = useTemplateContent(contentId);

  if (!content || !template) return null;

  return (
    <div>
      <h1>{content.title}</h1>

      {/* Render grouped fields */}
      {template.groupOrder.map(groupId => {
        const group = template.groups[groupId];
        if (!group) return null;

        // Get fields for this group
        const groupFields = group.fieldIds
          .map(fieldId => template.fields[fieldId])
          .filter(Boolean);

        // Get values using camelCase group name
        const groupName = normalizeGroupName(group.name);
        const groupValues = content.templateValues?.groups?.[groupName] || {};

        return (
          <section key={groupId}>
            <h2>{group.name}</h2>
            {groupFields.map(field => (
              <div key={field.id}>
                <label>{field.label}</label>
                <div>{groupValues[field.name]}</div>
              </div>
            ))}
          </section>
        );
      })}
    </div>
  );
}

Components

OfflineIndicator

A customizable component to display the current online/offline status:

import { useKioskContent, OfflineIndicator } from '@bigdigital/kiosk-content-sdk';

function App() {
  const { isOnline } = useKioskContent(config);

  return (
    <div>
      <OfflineIndicator 
        isOnline={isOnline}
        onlineText="Connected" // Optional custom text
        offlineText="Working Offline" // Optional custom text
        className="my-custom-class" // Optional CSS class
        style={{ position: 'fixed', top: '1rem', right: '1rem' }} // Optional inline styles
      />
      {/* Rest of your app */}
    </div>
  );
}

Configuration Options

KioskConfig

interface KioskConfig {
  projectId: string;
  apiKey: string;
  authDomain?: string;
  offlineSupport?: boolean;
  cacheStrategy?: "memory" | "local" | "none";
  syncInterval?: number; // in milliseconds
  cacheMaxAge?: number; // in milliseconds, defaults to 5 minutes
  projectDefaults?: {
    filterDefaults?: Record<string, any>;
    paginationDefaults?: {
      limit: number;
      orderBy?: string;
      orderDirection?: 'asc' | 'desc';
    };
  };
}

Offline Support

The SDK provides robust offline support with intelligent synchronization:

import { useOfflineContent } from '@bigdigital/kiosk-content-sdk';

function OfflineApp() {
  const { 
    content,
    syncStatus,
    lastSynced,
    error,
    forceSyncContent 
  } = useOfflineContent({
    projectId: "your-project-id",
    apiKey: "your-api-key",
    offlineSupport: true,
    cacheStrategy: "local",
    syncInterval: 300000,
    cacheMaxAge: 600000
  });

  return (
    <div>
      <div className="sync-status">
        <span>Status: {syncStatus}</span>
        {lastSynced && (
          <span>Last Synced: {new Date(lastSynced).toLocaleString()}</span>
        )}
        <button onClick={forceSyncContent}>
          Force Sync
        </button>
      </div>

      {error && <div className="error">Error: {error.message}</div>}

      <div className="content">
        {content.map(item => (
          <ContentCard 
            key={item.id} 
            content={item}
            isOffline={!navigator.onLine}
          />
        ))}
      </div>
    </div>
  );
}