Refactor and enhance UI components across multiple pages
- Improved import organization and formatting in PlaylistsPage, RegisterPage, SoundsPage, SettingsPage, and UsersPage for better readability. - Added error handling and user feedback with toast notifications in SoundsPage and SettingsPage. - Enhanced user experience by implementing debounced search functionality in PlaylistsPage and SoundsPage. - Updated the layout and structure of forms in SettingsPage and UsersPage for better usability. - Improved accessibility and semantics by ensuring proper labeling and descriptions in forms. - Fixed minor bugs related to state management and API calls in various components.
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { AUTH_EVENTS, authEvents } from '../events'
|
||||
import { API_CONFIG } from './config'
|
||||
import { createApiError, NetworkError, TimeoutError } from './errors'
|
||||
import { NetworkError, TimeoutError, createApiError } from './errors'
|
||||
import type { ApiClient, ApiRequestConfig, HttpMethod } from './types'
|
||||
import { authEvents, AUTH_EVENTS } from '../events'
|
||||
|
||||
export class BaseApiClient implements ApiClient {
|
||||
private refreshPromise: Promise<void> | null = null
|
||||
@@ -11,9 +11,12 @@ export class BaseApiClient implements ApiClient {
|
||||
this.baseURL = baseURL
|
||||
}
|
||||
|
||||
private buildURL(endpoint: string, params?: Record<string, string | number | boolean | undefined>): string {
|
||||
private buildURL(
|
||||
endpoint: string,
|
||||
params?: Record<string, string | number | boolean | undefined>,
|
||||
): string {
|
||||
let url: URL
|
||||
|
||||
|
||||
if (this.baseURL) {
|
||||
// Full base URL provided
|
||||
url = new URL(endpoint, this.baseURL)
|
||||
@@ -21,7 +24,7 @@ export class BaseApiClient implements ApiClient {
|
||||
// Use relative URL (for reverse proxy)
|
||||
url = new URL(endpoint, window.location.origin)
|
||||
}
|
||||
|
||||
|
||||
if (params) {
|
||||
Object.entries(params).forEach(([key, value]) => {
|
||||
if (value !== undefined) {
|
||||
@@ -29,7 +32,7 @@ export class BaseApiClient implements ApiClient {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
return this.baseURL ? url.toString() : url.pathname + url.search
|
||||
}
|
||||
|
||||
@@ -37,7 +40,7 @@ export class BaseApiClient implements ApiClient {
|
||||
method: HttpMethod,
|
||||
endpoint: string,
|
||||
data?: unknown,
|
||||
config: ApiRequestConfig = {}
|
||||
config: ApiRequestConfig = {},
|
||||
): Promise<T> {
|
||||
const {
|
||||
params,
|
||||
@@ -84,40 +87,43 @@ export class BaseApiClient implements ApiClient {
|
||||
await this.handleTokenRefresh()
|
||||
// Retry the original request
|
||||
const retryResponse = await fetch(url, requestConfig)
|
||||
|
||||
|
||||
if (!retryResponse.ok) {
|
||||
const errorData = await this.safeParseJSON(retryResponse)
|
||||
throw createApiError(retryResponse, errorData)
|
||||
}
|
||||
|
||||
return await this.safeParseJSON(retryResponse) as T
|
||||
|
||||
return (await this.safeParseJSON(retryResponse)) as T
|
||||
} catch (refreshError) {
|
||||
this.handleAuthenticationFailure()
|
||||
throw refreshError
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const errorData = await this.safeParseJSON(response)
|
||||
throw createApiError(response, errorData)
|
||||
}
|
||||
|
||||
// Handle empty responses (204 No Content, etc.)
|
||||
if (response.status === 204 || response.headers.get('content-length') === '0') {
|
||||
if (
|
||||
response.status === 204 ||
|
||||
response.headers.get('content-length') === '0'
|
||||
) {
|
||||
return {} as T
|
||||
}
|
||||
|
||||
return await this.safeParseJSON(response) as T
|
||||
return (await this.safeParseJSON(response)) as T
|
||||
} catch (error) {
|
||||
clearTimeout(timeoutId)
|
||||
|
||||
|
||||
if ((error as Error).name === 'AbortError') {
|
||||
throw new TimeoutError()
|
||||
}
|
||||
|
||||
|
||||
if (error instanceof TypeError && error.message.includes('fetch')) {
|
||||
throw new NetworkError()
|
||||
}
|
||||
|
||||
|
||||
throw error
|
||||
}
|
||||
}
|
||||
@@ -138,7 +144,7 @@ export class BaseApiClient implements ApiClient {
|
||||
}
|
||||
|
||||
this.refreshPromise = this.performTokenRefresh()
|
||||
|
||||
|
||||
try {
|
||||
await this.refreshPromise
|
||||
} finally {
|
||||
@@ -147,11 +153,14 @@ export class BaseApiClient implements ApiClient {
|
||||
}
|
||||
|
||||
private async performTokenRefresh(): Promise<void> {
|
||||
const response = await fetch(`${this.baseURL}${API_CONFIG.ENDPOINTS.AUTH.REFRESH}`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
})
|
||||
const response = await fetch(
|
||||
`${this.baseURL}${API_CONFIG.ENDPOINTS.AUTH.REFRESH}`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
credentials: 'include',
|
||||
},
|
||||
)
|
||||
|
||||
if (!response.ok) {
|
||||
throw createApiError(response, await this.safeParseJSON(response))
|
||||
@@ -165,7 +174,7 @@ export class BaseApiClient implements ApiClient {
|
||||
// Only redirect if we're not already on auth pages to prevent infinite loops
|
||||
const currentPath = window.location.pathname
|
||||
const authPaths = ['/login', '/register', '/auth/callback']
|
||||
|
||||
|
||||
if (!authPaths.includes(currentPath)) {
|
||||
window.location.href = '/login'
|
||||
}
|
||||
@@ -176,15 +185,27 @@ export class BaseApiClient implements ApiClient {
|
||||
return this.request<T>('GET', endpoint, undefined, config)
|
||||
}
|
||||
|
||||
async post<T>(endpoint: string, data?: unknown, config?: ApiRequestConfig): Promise<T> {
|
||||
async post<T>(
|
||||
endpoint: string,
|
||||
data?: unknown,
|
||||
config?: ApiRequestConfig,
|
||||
): Promise<T> {
|
||||
return this.request<T>('POST', endpoint, data, config)
|
||||
}
|
||||
|
||||
async put<T>(endpoint: string, data?: unknown, config?: ApiRequestConfig): Promise<T> {
|
||||
async put<T>(
|
||||
endpoint: string,
|
||||
data?: unknown,
|
||||
config?: ApiRequestConfig,
|
||||
): Promise<T> {
|
||||
return this.request<T>('PUT', endpoint, data, config)
|
||||
}
|
||||
|
||||
async patch<T>(endpoint: string, data?: unknown, config?: ApiRequestConfig): Promise<T> {
|
||||
async patch<T>(
|
||||
endpoint: string,
|
||||
data?: unknown,
|
||||
config?: ApiRequestConfig,
|
||||
): Promise<T> {
|
||||
return this.request<T>('PATCH', endpoint, data, config)
|
||||
}
|
||||
|
||||
@@ -203,4 +224,4 @@ export class BaseApiClient implements ApiClient {
|
||||
}
|
||||
|
||||
// Default API client instance
|
||||
export const apiClient = new BaseApiClient()
|
||||
export const apiClient = new BaseApiClient()
|
||||
|
||||
Reference in New Issue
Block a user