Skip to content

Example app demonstrating the Typefully API - view scheduled posts across multiple accounts in a unified calendar

License

Notifications You must be signed in to change notification settings

typefully/multi-account-viewer

Repository files navigation

Typefully Multi-Account Viewer

TypeScript React Vite Typefully API

A read-only example app demonstrating how to integrate with the Typefully API. View scheduled posts across multiple Typefully accounts in a unified calendar.

Typefully Multi-Account Viewer

What This Example Demonstrates

This app showcases common patterns for working with the Typefully API:

  • Authentication - Bearer token authentication with validation
  • Fetching Social Sets - Retrieve all accounts accessible with an API token
  • Fetching Scheduled Drafts - List scheduled posts with filtering and sorting
  • Multi-Account Workflows - Aggregate data from multiple accounts in parallel
  • React Query Integration - Caching, auto-refresh, and loading states

Quick Start

Prerequisites

Get Your API Key

  1. Go to Typefully API Settings
  2. Click "Generate new key"
  3. Copy the token (starts with tfp_)

Run the App

git clone https://github.com/typefully/multi-account-viewer.git
cd multi-account-viewer
pnpm install
pnpm dev

Open http://localhost:5173 and paste your API key.

How It Works

Architecture Overview

┌─────────────────────────────────────────────────────────────┐
│                         App.tsx                              │
│  Orchestrates views, manages token presence, shows modals    │
└─────────────────────────────────────────────────────────────┘
                              │
        ┌─────────────────────┼─────────────────────┐
        ▼                     ▼                     ▼
┌──────────────┐    ┌──────────────┐    ┌──────────────────┐
│  CalendarView │    │   ListView   │    │  AccountSelector │
│  Week layout  │    │  Date groups │    │  Multi-select    │
└──────────────┘    └──────────────┘    └──────────────────┘
        │                     │                     │
        └─────────────────────┼─────────────────────┘
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                     React Query Hooks                        │
│  useSocialSets() - fetches accounts                         │
│  useScheduledDrafts() - fetches posts in parallel           │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                     TypefullyClient                          │
│  API client with Bearer auth, error handling                 │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                     Zustand Store                            │
│  Persists: token, selected accounts, view mode, theme        │
└─────────────────────────────────────────────────────────────┘

Key Code Patterns

1. API Client Setup

The API client (src/api/client.ts) handles authentication and requests:

import type { SocialSet, Draft, PaginatedResponse } from './types';

const API_BASE = 'https://api.typefully.com/v2';

export class TypefullyClient {
  private token: string;

  constructor(token: string) {
    this.token = token;
  }

  private async fetch<T>(endpoint: string, params?: Record<string, string>): Promise<T> {
    const url = new URL(`${API_BASE}${endpoint}`);
    if (params) {
      Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v));
    }

    const res = await fetch(url.toString(), {
      headers: {
        'Authorization': `Bearer ${this.token}`,
        'Content-Type': 'application/json',
      },
    });

    if (!res.ok) {
      throw new ApiError(res.status, await res.text());
    }

    return res.json();
  }

  // Fetch all accessible accounts
  async getSocialSets(): Promise<PaginatedResponse<SocialSet>> {
    return this.fetch('/social-sets', { limit: '50' });
  }

  // Fetch scheduled drafts for an account
  async getScheduledDrafts(socialSetId: number): Promise<PaginatedResponse<Draft>> {
    return this.fetch(`/social-sets/${socialSetId}/drafts`, {
      status: 'scheduled',
      limit: '50',
      order_by: 'scheduled_date',
    });
  }
}

2. React Query for Data Fetching

We use TanStack React Query for caching and background updates (src/hooks/useSocialSets.ts):

import { useQuery } from '@tanstack/react-query';
import { TypefullyClient } from '../api/client';
import { useStore } from '../store/store';

export function useSocialSets() {
  const token = useStore(state => state.token);

  return useQuery({
    queryKey: ['social-sets', token],
    queryFn: async () => {
      const client = new TypefullyClient(token!);
      const response = await client.getSocialSets();
      return response.results;
    },
    enabled: !!token,           // Only fetch when token exists
    staleTime: 5 * 60 * 1000,   // Cache for 5 minutes
  });
}

3. Parallel Requests for Multiple Accounts

Fetching drafts from multiple accounts in parallel (src/hooks/useDrafts.ts):

export function useScheduledDrafts(accounts: SocialSet[]) {
  const token = useStore(state => state.token);
  const selectedAccountIds = useStore(state => state.selectedAccountIds);

  return useQuery({
    queryKey: ['scheduled-drafts', token, selectedAccountIds],
    queryFn: async () => {
      const client = new TypefullyClient(token!);
      const selectedAccounts = accounts.filter(a => selectedAccountIds.includes(a.id));

      // Fetch drafts from all selected accounts in parallel
      const results = await Promise.all(
        selectedAccounts.map(account => client.getScheduledDrafts(account.id))
      );

      // Combine and sort all drafts
      return results
        .flatMap((r, i) => r.results.map(draft => ({
          ...draft,
          account: selectedAccounts[i],
          accountColor: getAccountColor(selectedAccounts[i].id),
        })))
        .sort((a, b) =>
          new Date(a.scheduled_date!).getTime() - new Date(b.scheduled_date!).getTime()
        );
    },
    enabled: !!token && selectedAccountIds.length > 0,
    refetchInterval: 60 * 1000,  // Auto-refresh every minute
  });
}

4. State Persistence with Zustand

User preferences are persisted to localStorage (src/store/store.ts):

import { create } from 'zustand';
import { persist } from 'zustand/middleware';

interface Store {
  token: string | null;
  selectedAccountIds: number[];
  viewMode: 'calendar' | 'list';
  theme: 'light' | 'dark' | 'system';
  // ... actions
}

export const useStore = create<Store>()(
  persist(
    (set) => ({
      token: null,
      selectedAccountIds: [],
      viewMode: 'calendar',
      theme: 'system',
      setToken: (token) => set({ token }),
      toggleAccount: (id) => set((state) => ({
        selectedAccountIds: state.selectedAccountIds.includes(id)
          ? state.selectedAccountIds.filter(i => i !== id)
          : [...state.selectedAccountIds, id]
      })),
      // ...
    }),
    { name: 'typefully-viewer-storage' }
  )
);

API Endpoints Used

Endpoint Description
GET /v2/social-sets List all accounts accessible with the API token
GET /v2/social-sets/{id}/drafts?status=scheduled Get scheduled drafts for an account

See the full Typefully API documentation for all available endpoints.

Project Structure

src/
├── api/
│   ├── client.ts      # TypefullyClient - API wrapper with auth
│   └── types.ts       # TypeScript interfaces for API responses
├── components/
│   ├── ui/            # Button, Input, Modal, Avatar, Badge, Skeleton
│   ├── layout/        # Header (nav, theme toggle), Sidebar
│   ├── onboarding/    # WelcomeModal (API key input)
│   ├── accounts/      # AccountSelector (multi-select with teams)
│   ├── calendar/      # CalendarView (week-row layout)
│   ├── timeline/      # ListView (date-grouped list)
│   └── drafts/        # DraftCard, PlatformIcons
├── hooks/
│   ├── useSocialSets.ts    # React Query hook for accounts
│   ├── useDrafts.ts        # React Query hook for drafts
│   └── useTheme.ts         # Theme management
├── store/
│   └── store.ts       # Zustand store with persistence
└── lib/
    └── utils.ts       # Utility functions (cn, truncate)

Tech Stack

Scripts

pnpm dev      # Start development server
pnpm build    # Type-check and build for production
pnpm preview  # Preview production build
pnpm lint     # Run ESLint

License

MIT - See LICENSE for details.

About

Example app demonstrating the Typefully API - view scheduled posts across multiple accounts in a unified calendar

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 2

  •  
  •