Track B — Auth & User System (complete): - User registration with bcrypt + Turnstile verification - JWT access/refresh token flow with httpOnly cookie rotation - Redis refresh token blocklist for logout - User profile + settings update endpoints (username, email) - API key generation with bcrypt hashing (ff_key_ prefix) - BYOK key management with AES-256-GCM encryption at rest - Free tier rate limiting (5 shaders/month) - Tier-gated endpoints (Pro/Studio for BYOK, API keys, bounty posting) Track C — Shader Submission & Renderer (complete): - GLSL validator: entry point check, banned extensions, infinite loop detection, brace balancing, loop bound warnings, code length limits - Puppeteer/headless Chromium renderer with Shadertoy-compatible uniform injection (iTime, iResolution, iMouse), WebGL2 with SwiftShader fallback - Shader compilation error detection via page title signaling - Thumbnail capture at t=1s, preview frame at t=duration - Renderer client service for API→renderer HTTP communication - Shader submission pipeline: validate GLSL → create record → enqueue render job - Desire fulfillment linking on shader submit - Re-validation and re-render on shader code update - Fork endpoint copies code, tags, metadata, enqueues new render Track D — Frontend Shell (complete): - React 18 + Vite + TypeScript + Tailwind CSS + TanStack Query + Zustand - Dark theme with custom fracta color palette and surface tones - Responsive layout with sticky navbar, gradient branding - Auth: Login + Register pages with JWT token management - API client with automatic 401 refresh interceptor - ShaderCanvas: Full WebGL2 renderer component with Shadertoy uniforms, mouse tracking, ResizeObserver, debounced recompilation, error callbacks - GLSL Editor: Split pane (code textarea + live preview), 400ms debounced preview, metadata panel (description, tags, type), GLSL validation errors, shader publish flow, fork-from-existing support - Feed: Infinite scroll with IntersectionObserver sentinel, dwell time tracking, skeleton loading states, empty state with CTA - Explore: Search + tag filter + sort tabs (trending/new/top), grid layout - ShaderDetail: Full-screen preview, vote controls, view source toggle, fork button - Bounties: Desire queue list sorted by heat score, status badges, tip display - BountyDetail: Single desire view with style hints, fulfill CTA - Profile: User header with avatar initial, shader grid - Settings: Account info, API key management (create/revoke/copy), subscription tiers - Generate: AI generation UI stub with prompt input, style controls, example prompts 76 files, ~5,700 lines of application code.
54 lines
1.4 KiB
TypeScript
54 lines
1.4 KiB
TypeScript
/**
|
|
* API client — Axios instance with JWT auth and automatic refresh.
|
|
*/
|
|
|
|
import axios from 'axios';
|
|
import { useAuthStore } from '@/stores/auth';
|
|
|
|
const API_BASE = import.meta.env.VITE_API_URL || '/api';
|
|
|
|
const api = axios.create({
|
|
baseURL: `${API_BASE}/v1`,
|
|
headers: { 'Content-Type': 'application/json' },
|
|
withCredentials: true, // Send refresh token cookie
|
|
});
|
|
|
|
// Request interceptor: attach access token
|
|
api.interceptors.request.use((config) => {
|
|
const token = useAuthStore.getState().accessToken;
|
|
if (token) {
|
|
config.headers.Authorization = `Bearer ${token}`;
|
|
}
|
|
return config;
|
|
});
|
|
|
|
// Response interceptor: auto-refresh on 401
|
|
api.interceptors.response.use(
|
|
(response) => response,
|
|
async (error) => {
|
|
const original = error.config;
|
|
|
|
if (error.response?.status === 401 && !original._retry) {
|
|
original._retry = true;
|
|
|
|
try {
|
|
const { data } = await axios.post(
|
|
`${API_BASE}/v1/auth/refresh`,
|
|
{},
|
|
{ withCredentials: true },
|
|
);
|
|
useAuthStore.getState().setAccessToken(data.access_token);
|
|
original.headers.Authorization = `Bearer ${data.access_token}`;
|
|
return api(original);
|
|
} catch {
|
|
useAuthStore.getState().logout();
|
|
window.location.href = '/login';
|
|
return Promise.reject(error);
|
|
}
|
|
}
|
|
|
|
return Promise.reject(error);
|
|
},
|
|
);
|
|
|
|
export default api;
|