mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-14 02:42:04 +08:00
feat: add Unsplash search hook and API proxy routes
- Implemented `useUnsplashSearch` hook for searching Unsplash photos with debounce. - Created Unsplash API client functions for searching photos and triggering downloads. - Added proxy routes for Unsplash API to handle search requests and background image uploads. - Introduced accessibility utilities for WCAG compliance checks and motion preference management. - Developed theme sharing module for encoding and decoding theme configurations as base64url strings.
This commit is contained in:
102
ccw/frontend/src/lib/unsplash.ts
Normal file
102
ccw/frontend/src/lib/unsplash.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* Unsplash API Client
|
||||
* Frontend functions to search Unsplash via the backend proxy.
|
||||
*/
|
||||
|
||||
export interface UnsplashPhoto {
|
||||
id: string;
|
||||
thumbUrl: string;
|
||||
smallUrl: string;
|
||||
regularUrl: string;
|
||||
photographer: string;
|
||||
photographerUrl: string;
|
||||
photoUrl: string;
|
||||
blurHash: string | null;
|
||||
downloadLocation: string;
|
||||
}
|
||||
|
||||
export interface UnsplashSearchResult {
|
||||
photos: UnsplashPhoto[];
|
||||
total: number;
|
||||
totalPages: number;
|
||||
}
|
||||
|
||||
function getCsrfToken(): string | null {
|
||||
const match = document.cookie.match(/XSRF-TOKEN=([^;]+)/);
|
||||
return match ? decodeURIComponent(match[1]) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Search Unsplash photos via backend proxy.
|
||||
*/
|
||||
export async function searchUnsplash(
|
||||
query: string,
|
||||
page = 1,
|
||||
perPage = 20
|
||||
): Promise<UnsplashSearchResult> {
|
||||
const params = new URLSearchParams({
|
||||
query,
|
||||
page: String(page),
|
||||
per_page: String(perPage),
|
||||
});
|
||||
|
||||
const response = await fetch(`/api/unsplash/search?${params}`, {
|
||||
credentials: 'same-origin',
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const body = await response.json().catch(() => ({}));
|
||||
throw new Error(body.error || `Unsplash search failed: ${response.status}`);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload a local image as background.
|
||||
* Sends raw binary to avoid base64 overhead.
|
||||
*/
|
||||
export async function uploadBackgroundImage(file: File): Promise<{ url: string; filename: string }> {
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': file.type,
|
||||
'X-Filename': file.name,
|
||||
};
|
||||
const csrfToken = getCsrfToken();
|
||||
if (csrfToken) {
|
||||
headers['X-CSRF-Token'] = csrfToken;
|
||||
}
|
||||
|
||||
const response = await fetch('/api/background/upload', {
|
||||
method: 'POST',
|
||||
headers,
|
||||
credentials: 'same-origin',
|
||||
body: file,
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const body = await response.json().catch(() => ({}));
|
||||
throw new Error(body.error || `Upload failed: ${response.status}`);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger Unsplash download event (required by API guidelines).
|
||||
*/
|
||||
export async function triggerUnsplashDownload(downloadLocation: string): Promise<void> {
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
};
|
||||
const csrfToken = getCsrfToken();
|
||||
if (csrfToken) {
|
||||
headers['X-CSRF-Token'] = csrfToken;
|
||||
}
|
||||
|
||||
await fetch('/api/unsplash/download', {
|
||||
method: 'POST',
|
||||
headers,
|
||||
credentials: 'same-origin',
|
||||
body: JSON.stringify({ downloadLocation }),
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user