Add Next.js web app
Feed with live updates and pinning, profiles with follow/pin, featured song picker, settings for all music services, and account deletion. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MzFywujuH9TiuXbr9vjoX8
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
'use client';
|
||||
|
||||
import useSWR from 'swr';
|
||||
import type { MeDTO } from '@musicshare/shared';
|
||||
|
||||
export const API_URL = process.env.NEXT_PUBLIC_API_URL ?? 'http://localhost:4000';
|
||||
|
||||
export class ApiError extends Error {
|
||||
constructor(
|
||||
public status: number,
|
||||
message: string,
|
||||
) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
|
||||
export async function api<T = unknown>(path: string, init: { method?: string; body?: unknown } = {}): Promise<T> {
|
||||
const res = await fetch(API_URL + path, {
|
||||
method: init.method ?? (init.body ? 'POST' : 'GET'),
|
||||
credentials: 'include',
|
||||
headers: init.body !== undefined ? { 'content-type': 'application/json' } : undefined,
|
||||
body: init.body !== undefined ? JSON.stringify(init.body) : undefined,
|
||||
});
|
||||
const data = await res.json().catch(() => ({}));
|
||||
if (!res.ok) throw new ApiError(res.status, (data as { error?: string }).error ?? res.statusText);
|
||||
return data as T;
|
||||
}
|
||||
|
||||
export const fetcher = <T,>(path: string) => api<T>(path);
|
||||
|
||||
export function useMe() {
|
||||
const { data, error, isLoading, mutate } = useSWR<MeDTO, ApiError>('/me', fetcher, {
|
||||
shouldRetryOnError: false,
|
||||
revalidateOnFocus: false,
|
||||
});
|
||||
return { me: data, isLoading, loggedOut: error?.status === 401, mutate };
|
||||
}
|
||||
Reference in New Issue
Block a user