A read-only example app demonstrating how to integrate with the Typefully API. View scheduled posts across multiple Typefully accounts in a unified calendar.
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
- Go to Typefully API Settings
- Click "Generate new key"
- Copy the token (starts with
tfp_)
git clone https://github.com/typefully/multi-account-viewer.git
cd multi-account-viewer
pnpm install
pnpm devOpen http://localhost:5173 and paste your API key.
┌─────────────────────────────────────────────────────────────┐
│ 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 │
└─────────────────────────────────────────────────────────────┘
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',
});
}
}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
});
}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
});
}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' }
)
);| 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.
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)
- Vite - Build tool
- React 19 - UI framework
- TypeScript - Type safety
- Tailwind CSS v4 - Styling
- TanStack React Query - Server state management
- Zustand - Client state management
- date-fns - Date utilities
- Lucide - Icons
pnpm dev # Start development server
pnpm build # Type-check and build for production
pnpm preview # Preview production build
pnpm lint # Run ESLintMIT - See LICENSE for details.
