Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.kash.bot/llms.txt

Use this file to discover all available pages before exploring further.

Complete React component examples for embedding Kash prediction markets with TypeScript support and hooks.

Basic Embed Component

Simple reusable thread embed component:
// components/KashEmbed.tsx
import { useEffect, useRef, useState } from 'react';

interface KashEmbedProps {
  threadId: string;
  source?: string;
  height?: number;
  className?: string;
  onReady?: (data: { threadId: string; marketCount: number; totalVolume: number }) => void;
  onMarketClick?: (data: { threadId: string; marketId: string }) => void;
}

export function KashEmbed({
  threadId,
  source = 'my-site',
  height = 900,
  className = '',
  onReady,
  onMarketClick,
}: KashEmbedProps) {
  const iframeRef = useRef<HTMLIFrameElement>(null);

  useEffect(() => {
    function handleMessage(event: MessageEvent) {
      if (event.origin !== 'https://app.kash.bot') return;

      const { type, data } = event.data;

      switch (type) {
        case 'kash-iframe-ready':
          onReady?.(data);
          break;
        case 'kash-market-clicked':
          onMarketClick?.(data);
          break;
      }
    }

    window.addEventListener('message', handleMessage);
    return () => window.removeEventListener('message', handleMessage);
  }, [onReady, onMarketClick]);

  const embedUrl = `https://app.kash.bot/threads/${threadId}/iframe?source=${encodeURIComponent(source)}`;

  return (
    <iframe
      ref={iframeRef}
      src={embedUrl}
      width="100%"
      height={height}
      frameBorder="0"
      className={className}
      title="Kash prediction markets"
      loading="lazy"
      allow="clipboard-write"
    />
  );
}
Usage:
import { KashEmbed } from '@/components/KashEmbed';

export default function MarketsPage() {
  return (
    <div className="container">
      <h1>2024 Election Markets</h1>
      <KashEmbed
        threadId="abc123"
        source="my-website"
        height={900}
        onReady={(data) => console.log(`Loaded ${data.marketCount} markets`)}
        onMarketClick={(data) => console.log(`Clicked: ${data.marketId}`)}
      />
    </div>
  );
}

Custom Hook for Embed Events

Reusable hook for listening to embed events:
// hooks/useKashEmbed.ts
import { useEffect, useState, useRef, useCallback } from 'react';

interface EmbedReadyData {
  threadId: string;
  marketCount: number;
  totalVolume: number;
}

interface MarketClickData {
  threadId: string;
  marketId: string;
}

interface UseKashEmbedOptions {
  onReady?: (data: EmbedReadyData) => void;
  onMarketClick?: (data: MarketClickData) => void;
}

export function useKashEmbed(options: UseKashEmbedOptions = {}) {
  const iframeRef = useRef<HTMLIFrameElement>(null);
  const [isReady, setIsReady] = useState(false);
  const [marketCount, setMarketCount] = useState<number | null>(null);

  useEffect(() => {
    function handleMessage(event: MessageEvent) {
      if (event.origin !== 'https://app.kash.bot') return;

      const { type, data } = event.data;

      switch (type) {
        case 'kash-iframe-ready':
          setIsReady(true);
          setMarketCount(data.marketCount);
          options.onReady?.(data);
          break;
        case 'kash-market-clicked':
          options.onMarketClick?.(data);
          break;
      }
    }

    window.addEventListener('message', handleMessage);
    return () => window.removeEventListener('message', handleMessage);
  }, [options.onReady, options.onMarketClick]);

  return {
    iframeRef,
    isReady,
    marketCount,
  };
}
Usage:
import { useKashEmbed } from '@/hooks/useKashEmbed';

export function MarketsWidget({ threadId }: { threadId: string }) {
  const { iframeRef, isReady, marketCount } = useKashEmbed({
    onReady: (data) => {
      console.log(`Loaded ${data.marketCount} markets`);
    },
    onMarketClick: (data) => {
      console.log(`Market clicked: ${data.marketId}`);
    },
  });

  return (
    <div>
      <div className="status">
        {isReady 
          ? `✅ ${marketCount} markets loaded` 
          : 'Loading...'}
      </div>
      
      <iframe
        ref={iframeRef}
        src={`https://app.kash.bot/threads/${threadId}/iframe?source=my-site`}
        width="100%"
        height={900}
        frameBorder="0"
      />
    </div>
  );
}

Responsive Sidebar Widget

Compact widget component:
// components/MarketWidget.tsx
import { useState } from 'react';
import { useKashEmbed } from '@/hooks/useKashEmbed';

interface MarketWidgetProps {
  threadId: string;
  title: string;
}

export function MarketWidget({ threadId, title }: MarketWidgetProps) {
  const [status, setStatus] = useState('Loading...');

  const { iframeRef, isReady, marketCount } = useKashEmbed({
    onReady: (data) => {
      setStatus(`${data.marketCount} markets`);
    },
  });

  return (
    <div className="market-widget">
      <div className="widget-header">
        <h3 className="widget-title">📊 {title}</h3>
        <span className="widget-badge">{status}</span>
      </div>

      <iframe
        ref={iframeRef}
        src={`https://app.kash.bot/threads/${threadId}/iframe?source=my-site`}
        width="100%"
        height={400}
        frameBorder="0"
        className="widget-iframe"
      />

      <style jsx>{`
        .market-widget {
          background: white;
          border-radius: 12px;
          padding: 16px;
          box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
          max-width: 350px;
        }

        .widget-header {
          display: flex;
          align-items: center;
          justify-content: space-between;
          margin-bottom: 12px;
        }

        .widget-title {
          font-size: 18px;
          font-weight: 600;
          margin: 0;
        }

        .widget-badge {
          background: #dbeafe;
          color: #1e40af;
          padding: 4px 10px;
          border-radius: 12px;
          font-size: 12px;
          font-weight: 500;
        }

        .widget-iframe {
          border-radius: 8px;
          border: 0;
        }
      `}</style>
    </div>
  );
}

Multi-Thread Dashboard

Display multiple threads with dynamic loading:
// components/MarketDashboard.tsx
import { useState, useEffect } from 'react';
import { KashEmbed } from './KashEmbed';

interface Thread {
  id: string;
  title: string;
  category: string;
  marketCount: number;
}

export function MarketDashboard() {
  const [threads, setThreads] = useState<Thread[]>([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    async function fetchThreads() {
      try {
        const response = await fetch(
          'https://app.kash.bot/api/threads?status=active&limit=6'
        );
        const data = await response.json();
        setThreads(data.threads);
      } catch (error) {
        console.error('Failed to fetch threads:', error);
      } finally {
        setLoading(false);
      }
    }

    fetchThreads();
  }, []);

  if (loading) {
    return <div className="loading">Loading threads...</div>;
  }

  return (
    <div className="dashboard">
      <h1>Market Dashboard</h1>

      <div className="thread-grid">
        {threads.map((thread) => (
          <div key={thread.id} className="thread-card">
            <div className="thread-header">
              <h2>{thread.title}</h2>
              <span className="thread-category">{thread.category}</span>
            </div>

            <KashEmbed
              threadId={thread.id}
              height={500}
              onReady={(data) => {
                console.log(`Thread ${thread.id}: ${data.marketCount} markets`);
              }}
            />
          </div>
        ))}
      </div>

      <style jsx>{`
        .dashboard {
          max-width: 1400px;
          margin: 0 auto;
          padding: 20px;
        }

        .thread-grid {
          display: grid;
          grid-template-columns: repeat(auto-fit, minmax(400px, 1fr));
          gap: 20px;
          margin-top: 20px;
        }

        .thread-card {
          background: white;
          border-radius: 8px;
          padding: 16px;
          box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
        }

        .thread-header {
          margin-bottom: 12px;
        }

        .thread-header h2 {
          font-size: 18px;
          margin: 0 0 4px 0;
        }

        .thread-category {
          font-size: 13px;
          color: #6b7280;
          text-transform: uppercase;
          letter-spacing: 0.5px;
        }
      `}</style>
    </div>
  );
}

Error Boundary Wrapper

Graceful error handling:
// components/EmbedErrorBoundary.tsx
import React, { Component, ReactNode } from 'react';

interface Props {
  children: ReactNode;
  fallback?: ReactNode;
}

interface State {
  hasError: boolean;
  error?: Error;
}

export class EmbedErrorBoundary extends Component<Props, State> {
  constructor(props: Props) {
    super(props);
    this.state = { hasError: false };
  }

  static getDerivedStateFromError(error: Error): State {
    return { hasError: true, error };
  }

  componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
    console.error('Embed error:', error, errorInfo);
  }

  render() {
    if (this.state.hasError) {
      return (
        this.props.fallback || (
          <div className="error-container">
            <h3>Failed to load markets</h3>
            <p>Please try refreshing the page.</p>
            <button onClick={() => this.setState({ hasError: false })}>
              Retry
            </button>
          </div>
        )
      );
    }

    return this.props.children;
  }
}
Usage:
<EmbedErrorBoundary>
  <KashEmbed threadId="abc123" />
</EmbedErrorBoundary>

SSR-Safe Embed (Next.js)

Server-side rendering compatible component:
// components/SSRSafeEmbed.tsx
import dynamic from 'next/dynamic';

const KashEmbedClient = dynamic(
  () => import('./KashEmbed').then((mod) => mod.KashEmbed),
  {
    ssr: false,
    loading: () => (
      <div className="embed-skeleton">
        <div className="skeleton-header" />
        <div className="skeleton-card" />
        <div className="skeleton-card" />
        <div className="skeleton-card" />
      </div>
    ),
  }
);

export function SSRSafeEmbed(props: any) {
  return <KashEmbedClient {...props} />;
}

Complete Page Example

Full Next.js page with all features:
// app/markets/page.tsx
'use client';

import { useState } from 'react';
import { useKashEmbed } from '@/hooks/useKashEmbed';
import { EmbedErrorBoundary } from '@/components/EmbedErrorBoundary';

export default function MarketsPage() {
  const [status, setStatus] = useState('Loading markets...');

  const { iframeRef, isReady, marketCount } = useKashEmbed({
    onReady: (data) => {
      setStatus(`✅ Loaded ${data.marketCount} markets`);
    },
    onMarketClick: (data) => {
      setStatus(`👆 Clicked: ${data.marketId}`);
    },
  });

  return (
    <div className="container mx-auto px-4 py-8">
      <header className="mb-8">
        <h1 className="text-3xl font-bold mb-2">Prediction Markets</h1>
        <p className="text-gray-600">
          Live probabilities on politics, sports, and current events
        </p>
      </header>

      <div className="bg-blue-50 border-l-4 border-blue-500 p-4 mb-4 text-sm">
        {status}
      </div>

      <EmbedErrorBoundary>
        <iframe
          ref={iframeRef}
          src="https://app.kash.bot/threads/abc123/iframe?source=my-website"
          width="100%"
          height={900}
          frameBorder="0"
          className="rounded-lg border border-gray-200 shadow-sm"
          title="Prediction markets"
        />
      </EmbedErrorBoundary>
    </div>
  );
}

Next Steps

HTML Examples

Vanilla JavaScript implementations

Iframe Guide

Complete iframe embedding guide