feat: update API client and remove unused services; enhance error handling and configuration
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@@ -12,6 +12,11 @@ dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Environment variables
|
||||
.env.local
|
||||
.env.development.local
|
||||
.env.production.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
|
||||
69
README.md
69
README.md
@@ -1,69 +0,0 @@
|
||||
# React + TypeScript + Vite
|
||||
|
||||
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
||||
|
||||
Currently, two official plugins are available:
|
||||
|
||||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh
|
||||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
||||
|
||||
## Expanding the ESLint configuration
|
||||
|
||||
If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:
|
||||
|
||||
```js
|
||||
export default tseslint.config([
|
||||
globalIgnores(['dist']),
|
||||
{
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
extends: [
|
||||
// Other configs...
|
||||
|
||||
// Remove tseslint.configs.recommended and replace with this
|
||||
...tseslint.configs.recommendedTypeChecked,
|
||||
// Alternatively, use this for stricter rules
|
||||
...tseslint.configs.strictTypeChecked,
|
||||
// Optionally, add this for stylistic rules
|
||||
...tseslint.configs.stylisticTypeChecked,
|
||||
|
||||
// Other configs...
|
||||
],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
// other options...
|
||||
},
|
||||
},
|
||||
])
|
||||
```
|
||||
|
||||
You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:
|
||||
|
||||
```js
|
||||
// eslint.config.js
|
||||
import reactX from 'eslint-plugin-react-x'
|
||||
import reactDom from 'eslint-plugin-react-dom'
|
||||
|
||||
export default tseslint.config([
|
||||
globalIgnores(['dist']),
|
||||
{
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
extends: [
|
||||
// Other configs...
|
||||
// Enable lint rules for React
|
||||
reactX.configs['recommended-typescript'],
|
||||
// Enable lint rules for React DOM
|
||||
reactDom.configs.recommended,
|
||||
],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
// other options...
|
||||
},
|
||||
},
|
||||
])
|
||||
```
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import * as React from "react"
|
||||
import { Slot } from "@radix-ui/react-slot"
|
||||
import { cva, VariantProps } from "class-variance-authority"
|
||||
import { cva, type VariantProps } from "class-variance-authority"
|
||||
import { PanelLeftIcon } from "lucide-react"
|
||||
|
||||
import { useIsMobile } from "@/hooks/use-mobile"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useTheme } from "next-themes"
|
||||
import { Toaster as Sonner, ToasterProps } from "sonner"
|
||||
import { Toaster as Sonner, type ToasterProps } from "sonner"
|
||||
|
||||
const Toaster = ({ ...props }: ToasterProps) => {
|
||||
const { theme = "system" } = useTheme()
|
||||
|
||||
@@ -36,23 +36,13 @@ export function AuthProvider({ children }: AuthProviderProps) {
|
||||
}, [])
|
||||
|
||||
const login = async (credentials: LoginRequest) => {
|
||||
try {
|
||||
const user = await api.auth.login(credentials)
|
||||
setUser(user)
|
||||
} catch (error) {
|
||||
console.error('Login failed:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
const register = async (data: RegisterRequest) => {
|
||||
try {
|
||||
const user = await api.auth.register(data)
|
||||
setUser(user)
|
||||
} catch (error) {
|
||||
console.error('Registration failed:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
const logout = async () => {
|
||||
@@ -62,7 +52,6 @@ export function AuthProvider({ children }: AuthProviderProps) {
|
||||
|
||||
const value: AuthContextType = {
|
||||
user,
|
||||
token: user ? 'cookie-based' : null,
|
||||
login,
|
||||
register,
|
||||
logout,
|
||||
|
||||
@@ -1,214 +0,0 @@
|
||||
# API Library
|
||||
|
||||
A generic, maintainable API client library for the Soundboard application.
|
||||
|
||||
## Features
|
||||
|
||||
- **Generic HTTP Client**: Supports all REST methods with proper TypeScript typing
|
||||
- **Automatic Token Refresh**: Handles JWT token refresh automatically
|
||||
- **Error Handling**: Comprehensive error classes for different scenarios
|
||||
- **Modular Services**: Separate services for different API domains
|
||||
- **Configuration Management**: Centralized API configuration
|
||||
- **Backward Compatibility**: Legacy API service for existing code
|
||||
|
||||
## Usage
|
||||
|
||||
### New API (Recommended)
|
||||
|
||||
```typescript
|
||||
import { api } from '@/lib/api'
|
||||
|
||||
// Authentication
|
||||
const user = await api.auth.login({ email: 'user@example.com', password: 'password' })
|
||||
const currentUser = await api.auth.getMe()
|
||||
await api.auth.logout()
|
||||
|
||||
// Sounds
|
||||
const sounds = await api.sounds.list({ page: 1, size: 20 })
|
||||
const sound = await api.sounds.get(1)
|
||||
const newSound = await api.sounds.create({ title: 'My Sound', file: audioFile })
|
||||
|
||||
// Playlists
|
||||
const playlists = await api.playlists.list()
|
||||
const playlist = await api.playlists.create({ name: 'My Playlist' })
|
||||
|
||||
// Users (admin only)
|
||||
const users = await api.users.list()
|
||||
```
|
||||
|
||||
### Alternative Import Style
|
||||
|
||||
```typescript
|
||||
import { authService, soundsService } from '@/lib/api'
|
||||
|
||||
// Direct service imports
|
||||
const user = await authService.login(credentials)
|
||||
const sounds = await soundsService.list()
|
||||
```
|
||||
|
||||
### Direct Client Usage
|
||||
|
||||
```typescript
|
||||
import { apiClient } from '@/lib/api'
|
||||
|
||||
// Generic HTTP requests
|
||||
const data = await apiClient.get<MyType>('/api/v1/custom-endpoint')
|
||||
const result = await apiClient.post<ResponseType>('/api/v1/data', requestData)
|
||||
```
|
||||
|
||||
## Services
|
||||
|
||||
### AuthService (`api.auth`)
|
||||
|
||||
- `login(credentials)` - Authenticate user
|
||||
- `register(userData)` - Register new user
|
||||
- `getMe()` - Get current user
|
||||
- `logout()` - Sign out user
|
||||
- `getOAuthUrl(provider)` - Get OAuth authorization URL
|
||||
- `getOAuthProviders()` - Get available OAuth providers
|
||||
- `exchangeOAuthToken(request)` - Exchange OAuth code for auth cookies
|
||||
|
||||
### SoundsService (`api.sounds`)
|
||||
|
||||
- `list(params?)` - Get paginated sounds
|
||||
- `get(id)` - Get specific sound
|
||||
- `create(data)` - Create new sound
|
||||
- `update(id, data)` - Update sound
|
||||
- `delete(id)` - Delete sound
|
||||
- `upload(file, metadata?)` - Upload sound file
|
||||
|
||||
### PlaylistsService (`api.playlists`)
|
||||
|
||||
- `list(params?)` - Get paginated playlists
|
||||
- `get(id)` - Get specific playlist
|
||||
- `create(data)` - Create new playlist
|
||||
- `update(id, data)` - Update playlist
|
||||
- `delete(id)` - Delete playlist
|
||||
- `addSound(playlistId, soundData)` - Add sound to playlist
|
||||
- `removeSound(playlistId, soundId)` - Remove sound from playlist
|
||||
|
||||
### UsersService (`api.users`)
|
||||
|
||||
- `list(params?)` - Get paginated users (admin only)
|
||||
- `get(id)` - Get specific user
|
||||
- `update(id, data)` - Update user
|
||||
- `delete(id)` - Delete user (admin only)
|
||||
- `changePassword(userId, data)` - Change user password
|
||||
- `uploadAvatar(userId, file)` - Upload user avatar
|
||||
|
||||
## Error Handling
|
||||
|
||||
The library provides specific error classes for different scenarios:
|
||||
|
||||
```typescript
|
||||
import {
|
||||
ApiError,
|
||||
NetworkError,
|
||||
TimeoutError,
|
||||
ValidationError,
|
||||
AuthenticationError,
|
||||
AuthorizationError,
|
||||
NotFoundError,
|
||||
ServerError
|
||||
} from '@/lib/api'
|
||||
|
||||
try {
|
||||
await api.auth.login(credentials)
|
||||
} catch (error) {
|
||||
if (error instanceof AuthenticationError) {
|
||||
// Handle login failure
|
||||
} else if (error instanceof ValidationError) {
|
||||
// Handle validation errors
|
||||
console.log(error.fields) // Field-specific errors
|
||||
} else if (error instanceof NetworkError) {
|
||||
// Handle network issues
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
API configuration is centralized in `config.ts`:
|
||||
|
||||
```typescript
|
||||
export const API_CONFIG = {
|
||||
BASE_URL: import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000',
|
||||
TIMEOUT: 30000,
|
||||
RETRY_ATTEMPTS: 1,
|
||||
ENDPOINTS: {
|
||||
AUTH: { /* ... */ },
|
||||
SOUNDS: { /* ... */ },
|
||||
// ...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Request Configuration
|
||||
|
||||
All API methods accept optional configuration:
|
||||
|
||||
```typescript
|
||||
// Custom timeout
|
||||
await api.sounds.list({}, { timeout: 60000 })
|
||||
|
||||
// Skip authentication
|
||||
await api.auth.getOAuthProviders({}, { skipAuth: true })
|
||||
|
||||
// Custom headers
|
||||
await api.sounds.create(data, {
|
||||
headers: { 'X-Custom-Header': 'value' }
|
||||
})
|
||||
|
||||
// Query parameters
|
||||
await api.sounds.list({}, {
|
||||
params: { search: 'query', page: 1 }
|
||||
})
|
||||
```
|
||||
|
||||
## File Structure
|
||||
|
||||
```
|
||||
src/lib/api/
|
||||
├── index.ts # Main exports
|
||||
├── client.ts # Core HTTP client
|
||||
├── config.ts # API configuration
|
||||
├── types.ts # TypeScript types
|
||||
├── errors.ts # Error classes
|
||||
├── services/
|
||||
│ ├── auth.ts # Authentication service
|
||||
│ ├── sounds.ts # Sounds service
|
||||
│ ├── playlists.ts # Playlists service
|
||||
│ └── users.ts # Users service
|
||||
└── README.md # This file
|
||||
```
|
||||
|
||||
## Migration Guide
|
||||
|
||||
If migrating from an older API structure:
|
||||
|
||||
1. **Use the main API object:**
|
||||
```typescript
|
||||
import { api } from '@/lib/api'
|
||||
|
||||
// Authentication
|
||||
const user = await api.auth.login(credentials)
|
||||
|
||||
// Resources
|
||||
const sounds = await api.sounds.list()
|
||||
const playlists = await api.playlists.list()
|
||||
```
|
||||
|
||||
2. **Or import services directly:**
|
||||
```typescript
|
||||
import { authService, soundsService } from '@/lib/api'
|
||||
|
||||
const user = await authService.login(credentials)
|
||||
const sounds = await soundsService.list()
|
||||
```
|
||||
|
||||
3. **For custom requests, use the client:**
|
||||
```typescript
|
||||
import { apiClient } from '@/lib/api'
|
||||
|
||||
const data = await apiClient.get<MyType>('/api/v1/custom-endpoint')
|
||||
```
|
||||
@@ -81,7 +81,7 @@ export class BaseApiClient implements ApiClient {
|
||||
throw createApiError(retryResponse, errorData)
|
||||
}
|
||||
|
||||
return await this.safeParseJSON(retryResponse)
|
||||
return await this.safeParseJSON(retryResponse) as T
|
||||
} catch (refreshError) {
|
||||
this.handleAuthenticationFailure()
|
||||
throw refreshError
|
||||
@@ -97,7 +97,7 @@ export class BaseApiClient implements ApiClient {
|
||||
return {} as T
|
||||
}
|
||||
|
||||
return await this.safeParseJSON(response)
|
||||
return await this.safeParseJSON(response) as T
|
||||
} catch (error) {
|
||||
clearTimeout(timeoutId)
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// API Configuration
|
||||
export const API_CONFIG = {
|
||||
BASE_URL: import.meta.env.VITE_API_BASE_URL || 'http://localhost:8000',
|
||||
BASE_URL: 'http://localhost:8000',
|
||||
TIMEOUT: 30000, // 30 seconds
|
||||
RETRY_ATTEMPTS: 1,
|
||||
|
||||
@@ -17,27 +17,6 @@ export const API_CONFIG = {
|
||||
OAUTH_CALLBACK: (provider: string) => `/api/v1/auth/${provider}/callback`,
|
||||
EXCHANGE_OAUTH_TOKEN: '/api/v1/auth/exchange-oauth-token',
|
||||
},
|
||||
SOUNDS: {
|
||||
LIST: '/api/v1/sounds',
|
||||
CREATE: '/api/v1/sounds',
|
||||
GET: (id: string | number) => `/api/v1/sounds/${id}`,
|
||||
UPDATE: (id: string | number) => `/api/v1/sounds/${id}`,
|
||||
DELETE: (id: string | number) => `/api/v1/sounds/${id}`,
|
||||
UPLOAD: '/api/v1/sounds/upload',
|
||||
},
|
||||
PLAYLISTS: {
|
||||
LIST: '/api/v1/playlists',
|
||||
CREATE: '/api/v1/playlists',
|
||||
GET: (id: string | number) => `/api/v1/playlists/${id}`,
|
||||
UPDATE: (id: string | number) => `/api/v1/playlists/${id}`,
|
||||
DELETE: (id: string | number) => `/api/v1/playlists/${id}`,
|
||||
},
|
||||
USERS: {
|
||||
LIST: '/api/v1/users',
|
||||
GET: (id: string | number) => `/api/v1/users/${id}`,
|
||||
UPDATE: (id: string | number) => `/api/v1/users/${id}`,
|
||||
DELETE: (id: string | number) => `/api/v1/users/${id}`,
|
||||
},
|
||||
},
|
||||
} as const
|
||||
|
||||
|
||||
@@ -6,22 +6,13 @@ export * from './errors'
|
||||
|
||||
// Services
|
||||
export * from './services/auth'
|
||||
export * from './services/sounds'
|
||||
export * from './services/playlists'
|
||||
export * from './services/users'
|
||||
|
||||
// Main API object for convenient access
|
||||
import { authService } from './services/auth'
|
||||
import { soundsService } from './services/sounds'
|
||||
import { playlistsService } from './services/playlists'
|
||||
import { usersService } from './services/users'
|
||||
import { apiClient } from './client'
|
||||
|
||||
export const api = {
|
||||
auth: authService,
|
||||
sounds: soundsService,
|
||||
playlists: playlistsService,
|
||||
users: usersService,
|
||||
client: apiClient,
|
||||
} as const
|
||||
|
||||
|
||||
@@ -1,94 +0,0 @@
|
||||
import { apiClient } from '../client'
|
||||
import { API_CONFIG } from '../config'
|
||||
import type { PaginatedResponse } from '../types'
|
||||
import type { Sound } from './sounds'
|
||||
|
||||
export interface Playlist {
|
||||
id: number
|
||||
name: string
|
||||
description?: string
|
||||
user_id: number
|
||||
is_public: boolean
|
||||
sounds: Sound[]
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export interface CreatePlaylistRequest {
|
||||
name: string
|
||||
description?: string
|
||||
is_public?: boolean
|
||||
}
|
||||
|
||||
export interface UpdatePlaylistRequest {
|
||||
name?: string
|
||||
description?: string
|
||||
is_public?: boolean
|
||||
}
|
||||
|
||||
export interface PlaylistsListParams {
|
||||
page?: number
|
||||
size?: number
|
||||
search?: string
|
||||
user_id?: number
|
||||
is_public?: boolean
|
||||
}
|
||||
|
||||
export interface AddSoundToPlaylistRequest {
|
||||
sound_id: number
|
||||
}
|
||||
|
||||
export class PlaylistsService {
|
||||
/**
|
||||
* Get list of playlists with pagination
|
||||
*/
|
||||
async list(params?: PlaylistsListParams): Promise<PaginatedResponse<Playlist>> {
|
||||
return apiClient.get<PaginatedResponse<Playlist>>(API_CONFIG.ENDPOINTS.PLAYLISTS.LIST, {
|
||||
params: params as Record<string, string | number | boolean | undefined>
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific playlist by ID
|
||||
*/
|
||||
async get(id: string | number): Promise<Playlist> {
|
||||
return apiClient.get<Playlist>(API_CONFIG.ENDPOINTS.PLAYLISTS.GET(id))
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new playlist
|
||||
*/
|
||||
async create(data: CreatePlaylistRequest): Promise<Playlist> {
|
||||
return apiClient.post<Playlist>(API_CONFIG.ENDPOINTS.PLAYLISTS.CREATE, data)
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an existing playlist
|
||||
*/
|
||||
async update(id: string | number, data: UpdatePlaylistRequest): Promise<Playlist> {
|
||||
return apiClient.patch<Playlist>(API_CONFIG.ENDPOINTS.PLAYLISTS.UPDATE(id), data)
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a playlist
|
||||
*/
|
||||
async delete(id: string | number): Promise<void> {
|
||||
return apiClient.delete<void>(API_CONFIG.ENDPOINTS.PLAYLISTS.DELETE(id))
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a sound to a playlist
|
||||
*/
|
||||
async addSound(playlistId: string | number, data: AddSoundToPlaylistRequest): Promise<Playlist> {
|
||||
return apiClient.post<Playlist>(`${API_CONFIG.ENDPOINTS.PLAYLISTS.GET(playlistId)}/sounds`, data)
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a sound from a playlist
|
||||
*/
|
||||
async removeSound(playlistId: string | number, soundId: string | number): Promise<Playlist> {
|
||||
return apiClient.delete<Playlist>(`${API_CONFIG.ENDPOINTS.PLAYLISTS.GET(playlistId)}/sounds/${soundId}`)
|
||||
}
|
||||
}
|
||||
|
||||
export const playlistsService = new PlaylistsService()
|
||||
@@ -1,100 +0,0 @@
|
||||
import { apiClient } from '../client'
|
||||
import { API_CONFIG } from '../config'
|
||||
import type { PaginatedResponse } from '../types'
|
||||
|
||||
export interface Sound {
|
||||
id: number
|
||||
title: string
|
||||
description?: string
|
||||
file_url: string
|
||||
duration: number
|
||||
file_size: number
|
||||
mime_type: string
|
||||
play_count: number
|
||||
user_id: number
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}
|
||||
|
||||
export interface CreateSoundRequest {
|
||||
title: string
|
||||
description?: string
|
||||
file: File
|
||||
}
|
||||
|
||||
export interface UpdateSoundRequest {
|
||||
title?: string
|
||||
description?: string
|
||||
}
|
||||
|
||||
export interface SoundsListParams {
|
||||
page?: number
|
||||
size?: number
|
||||
search?: string
|
||||
user_id?: number
|
||||
}
|
||||
|
||||
export class SoundsService {
|
||||
/**
|
||||
* Get list of sounds with pagination
|
||||
*/
|
||||
async list(params?: SoundsListParams): Promise<PaginatedResponse<Sound>> {
|
||||
return apiClient.get<PaginatedResponse<Sound>>(API_CONFIG.ENDPOINTS.SOUNDS.LIST, {
|
||||
params: params as Record<string, string | number | boolean | undefined>
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific sound by ID
|
||||
*/
|
||||
async get(id: string | number): Promise<Sound> {
|
||||
return apiClient.get<Sound>(API_CONFIG.ENDPOINTS.SOUNDS.GET(id))
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new sound
|
||||
*/
|
||||
async create(data: CreateSoundRequest): Promise<Sound> {
|
||||
const formData = new FormData()
|
||||
formData.append('title', data.title)
|
||||
if (data.description) {
|
||||
formData.append('description', data.description)
|
||||
}
|
||||
formData.append('file', data.file)
|
||||
|
||||
return apiClient.post<Sound>(API_CONFIG.ENDPOINTS.SOUNDS.CREATE, formData)
|
||||
}
|
||||
|
||||
/**
|
||||
* Update an existing sound
|
||||
*/
|
||||
async update(id: string | number, data: UpdateSoundRequest): Promise<Sound> {
|
||||
return apiClient.patch<Sound>(API_CONFIG.ENDPOINTS.SOUNDS.UPDATE(id), data)
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a sound
|
||||
*/
|
||||
async delete(id: string | number): Promise<void> {
|
||||
return apiClient.delete<void>(API_CONFIG.ENDPOINTS.SOUNDS.DELETE(id))
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload a sound file
|
||||
*/
|
||||
async upload(file: File, metadata?: { title?: string; description?: string }): Promise<Sound> {
|
||||
const formData = new FormData()
|
||||
formData.append('file', file)
|
||||
|
||||
if (metadata?.title) {
|
||||
formData.append('title', metadata.title)
|
||||
}
|
||||
if (metadata?.description) {
|
||||
formData.append('description', metadata.description)
|
||||
}
|
||||
|
||||
return apiClient.post<Sound>(API_CONFIG.ENDPOINTS.SOUNDS.UPLOAD, formData)
|
||||
}
|
||||
}
|
||||
|
||||
export const soundsService = new SoundsService()
|
||||
@@ -1,74 +0,0 @@
|
||||
import type { User } from '@/types/auth'
|
||||
import { apiClient } from '../client'
|
||||
import { API_CONFIG } from '../config'
|
||||
import type { PaginatedResponse } from '../types'
|
||||
|
||||
export interface UpdateUserRequest {
|
||||
name?: string
|
||||
email?: string
|
||||
picture?: string
|
||||
}
|
||||
|
||||
export interface UsersListParams {
|
||||
page?: number
|
||||
size?: number
|
||||
search?: string
|
||||
role?: string
|
||||
is_active?: boolean
|
||||
}
|
||||
|
||||
export interface ChangePasswordRequest {
|
||||
current_password: string
|
||||
new_password: string
|
||||
}
|
||||
|
||||
export class UsersService {
|
||||
/**
|
||||
* Get list of users with pagination (admin only)
|
||||
*/
|
||||
async list(params?: UsersListParams): Promise<PaginatedResponse<User>> {
|
||||
return apiClient.get<PaginatedResponse<User>>(API_CONFIG.ENDPOINTS.USERS.LIST, {
|
||||
params: params as Record<string, string | number | boolean | undefined>
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a specific user by ID
|
||||
*/
|
||||
async get(id: string | number): Promise<User> {
|
||||
return apiClient.get<User>(API_CONFIG.ENDPOINTS.USERS.GET(id))
|
||||
}
|
||||
|
||||
/**
|
||||
* Update user profile
|
||||
*/
|
||||
async update(id: string | number, data: UpdateUserRequest): Promise<User> {
|
||||
return apiClient.patch<User>(API_CONFIG.ENDPOINTS.USERS.UPDATE(id), data)
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a user (admin only)
|
||||
*/
|
||||
async delete(id: string | number): Promise<void> {
|
||||
return apiClient.delete<void>(API_CONFIG.ENDPOINTS.USERS.DELETE(id))
|
||||
}
|
||||
|
||||
/**
|
||||
* Change user password
|
||||
*/
|
||||
async changePassword(userId: string | number, data: ChangePasswordRequest): Promise<void> {
|
||||
return apiClient.post<void>(`${API_CONFIG.ENDPOINTS.USERS.GET(userId)}/change-password`, data)
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload user avatar
|
||||
*/
|
||||
async uploadAvatar(userId: string | number, file: File): Promise<User> {
|
||||
const formData = new FormData()
|
||||
formData.append('avatar', file)
|
||||
|
||||
return apiClient.post<User>(`${API_CONFIG.ENDPOINTS.USERS.GET(userId)}/avatar`, formData)
|
||||
}
|
||||
}
|
||||
|
||||
export const usersService = new UsersService()
|
||||
@@ -20,15 +20,11 @@ export function AuthCallbackPage() {
|
||||
throw new Error('No authorization code received')
|
||||
}
|
||||
|
||||
console.log('Exchanging OAuth code for tokens...')
|
||||
|
||||
// Exchange the temporary code for proper auth cookies
|
||||
const result = await api.auth.exchangeOAuthToken({ code })
|
||||
console.log('Token exchange successful:', result)
|
||||
|
||||
// Now get the user info
|
||||
const user = await api.auth.getMe()
|
||||
console.log('User info retrieved:', user)
|
||||
|
||||
// Update auth context
|
||||
if (setUser) setUser(user)
|
||||
|
||||
@@ -40,7 +40,6 @@ export interface RegisterRequest {
|
||||
|
||||
export interface AuthContextType {
|
||||
user: User | null
|
||||
token: string | null
|
||||
login: (credentials: LoginRequest) => Promise<void>
|
||||
register: (data: RegisterRequest) => Promise<void>
|
||||
logout: () => Promise<void>
|
||||
|
||||
Reference in New Issue
Block a user