169 lines
4.6 KiB
TypeScript
169 lines
4.6 KiB
TypeScript
import { User, AuthResponse, PublicGolferStats, CommunityComparisonData } from '../types/auth';
|
|
import { Round, TrackingSettings, Course } from '../types/golf';
|
|
|
|
const AUTH_TOKEN_KEY = 'pinpoint_auth_token_v1';
|
|
|
|
export function getStoredToken(): string | null {
|
|
return localStorage.getItem(AUTH_TOKEN_KEY);
|
|
}
|
|
|
|
export function setStoredToken(token: string | null): void {
|
|
if (token) {
|
|
localStorage.setItem(AUTH_TOKEN_KEY, token);
|
|
} else {
|
|
localStorage.removeItem(AUTH_TOKEN_KEY);
|
|
}
|
|
}
|
|
|
|
async function request<T>(endpoint: string, options: RequestInit = {}): Promise<T> {
|
|
const token = getStoredToken();
|
|
const headers: Record<string, string> = {
|
|
'Content-Type': 'application/json',
|
|
...(options.headers as Record<string, string>),
|
|
};
|
|
|
|
if (token) {
|
|
headers['Authorization'] = `Bearer ${token}`;
|
|
}
|
|
|
|
const response = await fetch(endpoint, {
|
|
...options,
|
|
headers,
|
|
});
|
|
|
|
if (!response.ok) {
|
|
let errorMsg = `Request failed: ${response.statusText}`;
|
|
try {
|
|
const errorJson = await response.json();
|
|
if (errorJson && errorJson.error) {
|
|
errorMsg = errorJson.error;
|
|
}
|
|
} catch {
|
|
// Ignore json parse error
|
|
}
|
|
throw new Error(errorMsg);
|
|
}
|
|
|
|
return response.json();
|
|
}
|
|
|
|
export const authApi = {
|
|
async register(data: {
|
|
email: string;
|
|
username: string;
|
|
password: string;
|
|
name?: string;
|
|
handicapIndex?: number;
|
|
homeClub?: string;
|
|
isPublic?: boolean;
|
|
}): Promise<AuthResponse & { settings: TrackingSettings }> {
|
|
return request<AuthResponse & { settings: TrackingSettings }>('/api/auth/register', {
|
|
method: 'POST',
|
|
body: JSON.stringify(data),
|
|
});
|
|
},
|
|
|
|
async login(identifier: string, password: string): Promise<AuthResponse & { settings: TrackingSettings }> {
|
|
return request<AuthResponse & { settings: TrackingSettings }>('/api/auth/login', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ identifier, password }),
|
|
});
|
|
},
|
|
|
|
async getMe(): Promise<{ user: User; settings: TrackingSettings }> {
|
|
return request<{ user: User; settings: TrackingSettings }>('/api/auth/me');
|
|
},
|
|
|
|
async updateProfile(updates: Partial<User>): Promise<{ user: User }> {
|
|
return request<{ user: User }>('/api/auth/profile', {
|
|
method: 'PUT',
|
|
body: JSON.stringify(updates),
|
|
});
|
|
},
|
|
|
|
async ssoLogin(data: {
|
|
provider: string;
|
|
email?: string;
|
|
username?: string;
|
|
name?: string;
|
|
avatarUrl?: string;
|
|
authentikServerUrl?: string;
|
|
}): Promise<AuthResponse & { settings: TrackingSettings }> {
|
|
return request<AuthResponse & { settings: TrackingSettings }>('/api/auth/oauth/sso-login', {
|
|
method: 'POST',
|
|
body: JSON.stringify(data),
|
|
});
|
|
},
|
|
|
|
async getProviders(): Promise<{
|
|
providers: Array<{
|
|
id: string;
|
|
name: string;
|
|
icon: string;
|
|
isConfigured: boolean;
|
|
issuerUrl?: string;
|
|
authUrlEndpoint: string;
|
|
}>;
|
|
}> {
|
|
return request('/api/auth/oauth/providers');
|
|
},
|
|
|
|
async getSettings(): Promise<TrackingSettings> {
|
|
return request<TrackingSettings>('/api/auth/settings');
|
|
},
|
|
|
|
async updateSettings(settings: TrackingSettings): Promise<TrackingSettings> {
|
|
return request<TrackingSettings>('/api/auth/settings', {
|
|
method: 'PUT',
|
|
body: JSON.stringify(settings),
|
|
});
|
|
},
|
|
};
|
|
|
|
export const roundsApi = {
|
|
async getRounds(): Promise<Round[]> {
|
|
return request<Round[]>('/api/rounds');
|
|
},
|
|
|
|
async saveRound(round: Round): Promise<Round> {
|
|
return request<Round>('/api/rounds', {
|
|
method: 'POST',
|
|
body: JSON.stringify(round),
|
|
});
|
|
},
|
|
|
|
async syncBatch(rounds: Round[]): Promise<{ count: number; rounds: Round[] }> {
|
|
return request<{ count: number; rounds: Round[] }>('/api/rounds/sync-batch', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ rounds }),
|
|
});
|
|
},
|
|
|
|
async deleteRound(id: string): Promise<{ success: boolean }> {
|
|
return request<{ success: boolean }>(`/api/rounds/${id}`, {
|
|
method: 'DELETE',
|
|
});
|
|
},
|
|
|
|
async getCustomCourses(): Promise<Course[]> {
|
|
return request<Course[]>('/api/rounds/courses/custom');
|
|
},
|
|
|
|
async saveCustomCourse(course: Course): Promise<Course> {
|
|
return request<Course>('/api/rounds/courses/custom', {
|
|
method: 'POST',
|
|
body: JSON.stringify(course),
|
|
});
|
|
},
|
|
};
|
|
|
|
export const communityApi = {
|
|
async getLeaderboard(): Promise<{ totalCount: number; golfers: PublicGolferStats[] }> {
|
|
return request<{ totalCount: number; golfers: PublicGolferStats[] }>('/api/community/leaderboard');
|
|
},
|
|
|
|
async getComparison(): Promise<CommunityComparisonData> {
|
|
return request<CommunityComparisonData>('/api/community/compare');
|
|
},
|
|
};
|