A web app to help you design things, local, offline, on device. In your browser.

TypeScript 53.1% JSON 45.6% CSS 0.9% Markdown 0.3% JavaScript 0.1%
unsplash.ts 147 lines (4 KB)
// Unsplash API Client
// Free tier: 50 requests/hour (demo), unlimited on production approval
// Docs: https://unsplash.com/documentation

export interface UnsplashPhoto {
  id: string;
  created_at: string;
  updated_at: string;
  width: number;
  height: number;
  color: string;
  blur_hash: string;
  description: string | null;
  alt_description: string | null;
  urls: {
    raw: string;
    full: string;
    regular: string;
    small: string;
    thumb: string;
    small_s3: string;
  };
  links: {
    self: string;
    html: string;
    download: string;
    download_location: string;
  };
  user: {
    id: string;
    username: string;
    name: string;
    portfolio_url: string | null;
    bio: string | null;
    profile_image: {
      small: string;
      medium: string;
      large: string;
    };
    links: {
      self: string;
      html: string;
      photos: string;
    };
  };
}

export interface UnsplashSearchResponse {
  total: number;
  total_pages: number;
  results: UnsplashPhoto[];
}

const UNSPLASH_ACCESS_KEY = process.env.NEXT_PUBLIC_UNSPLASH_ACCESS_KEY || '';
const UNSPLASH_BASE_URL = 'https://api.unsplash.com';

async function fetchUnsplash<T>(endpoint: string): Promise<T> {
  if (!UNSPLASH_ACCESS_KEY) {
    throw new Error('Unsplash API key not configured. Add NEXT_PUBLIC_UNSPLASH_ACCESS_KEY to .env.local');
  }

  const response = await fetch(`${UNSPLASH_BASE_URL}${endpoint}`, {
    headers: {
      Authorization: `Client-ID ${UNSPLASH_ACCESS_KEY}`,
    },
  });

  if (!response.ok) {
    throw new Error(`Unsplash API error: ${response.status} ${response.statusText}`);
  }

  return response.json();
}

export async function searchUnsplashPhotos(
  query: string,
  options: {
    page?: number;
    per_page?: number;
    orientation?: 'landscape' | 'portrait' | 'squarish';
    color?: string;
    order_by?: 'relevant' | 'latest';
  } = {}
): Promise<UnsplashSearchResponse> {
  const params = new URLSearchParams({
    query,
    page: String(options.page || 1),
    per_page: String(options.per_page || 20),
  });

  if (options.orientation) params.set('orientation', options.orientation);
  if (options.color) params.set('color', options.color);
  if (options.order_by) params.set('order_by', options.order_by);

  return fetchUnsplash<UnsplashSearchResponse>(`/search/photos?${params}`);
}

export async function getRandomPhotos(
  options: {
    count?: number;
    query?: string;
    orientation?: 'landscape' | 'portrait' | 'squarish';
    featured?: boolean;
  } = {}
): Promise<UnsplashPhoto[]> {
  const params = new URLSearchParams({
    count: String(options.count || 10),
  });

  if (options.query) params.set('query', options.query);
  if (options.orientation) params.set('orientation', options.orientation);
  if (options.featured) params.set('featured', 'true');

  return fetchUnsplash<UnsplashPhoto[]>(`/photos/random?${params}`);
}

export async function getPhoto(id: string): Promise<UnsplashPhoto> {
  return fetchUnsplash<UnsplashPhoto>(`/photos/${id}`);
}

// Track download for Unsplash attribution requirement
export async function trackDownload(downloadLocation: string): Promise<void> {
  if (!UNSPLASH_ACCESS_KEY) return;

  try {
    await fetch(downloadLocation, {
      headers: {
        Authorization: `Client-ID ${UNSPLASH_ACCESS_KEY}`,
      },
    });
  } catch {
    // Silently fail - tracking is best effort
  }
}

// Predefined collections for quick browsing
export const UNSPLASH_COLLECTIONS = [
  { id: 'technology', label: 'Technology', query: 'technology software' },
  { id: 'gradients', label: 'Gradients', query: 'gradient abstract colorful' },
  { id: 'textures', label: 'Textures', query: 'texture pattern' },
  { id: 'dark', label: 'Dark Mode', query: 'dark moody black' },
  { id: 'minimal', label: 'Minimal', query: 'minimal white clean' },
  { id: 'nature', label: 'Nature', query: 'nature landscape scenic' },
  { id: '3d', label: '3D Renders', query: '3d render abstract' },
  { id: 'workspace', label: 'Workspace', query: 'desk workspace office' },
] as const;

About

A web app to help you design things, local, offline, on device. In your browser.

0 stars
0 forks