Initial commit: Meshkee dashboards monorepo.
Includes business, customer, and super-admin apps with shared packages and production deploy scripts. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
export interface ApiClientConfig {
|
||||
baseUrl: string
|
||||
accessTokenKey: string
|
||||
refreshTokenKey: string
|
||||
}
|
||||
|
||||
export class ApiError extends Error {
|
||||
status: number
|
||||
|
||||
constructor(message: string, status: number) {
|
||||
super(message)
|
||||
this.name = 'ApiError'
|
||||
this.status = status
|
||||
}
|
||||
}
|
||||
|
||||
export function isAbortError(err: unknown): boolean {
|
||||
return err instanceof DOMException && err.name === 'AbortError'
|
||||
}
|
||||
|
||||
function parseErrorMessage(payload: unknown, fallback: string) {
|
||||
if (!payload || typeof payload !== 'object') {
|
||||
return fallback
|
||||
}
|
||||
|
||||
const message = (payload as { message?: string | string[] }).message
|
||||
|
||||
if (Array.isArray(message)) {
|
||||
return message.join(', ')
|
||||
}
|
||||
|
||||
if (typeof message === 'string') {
|
||||
return message
|
||||
}
|
||||
|
||||
return fallback
|
||||
}
|
||||
|
||||
export interface ApiClient {
|
||||
getAccessToken: () => string | null
|
||||
getRefreshToken: () => string | null
|
||||
setTokens: (accessToken: string, refreshToken: string) => void
|
||||
clearTokens: () => void
|
||||
apiRequest: <T>(path: string, options?: ApiRequestOptions) => Promise<T>
|
||||
}
|
||||
|
||||
interface ApiRequestOptions extends Omit<RequestInit, 'body'> {
|
||||
body?: unknown
|
||||
auth?: boolean
|
||||
}
|
||||
|
||||
export function createApiClient(config: ApiClientConfig): ApiClient {
|
||||
const { baseUrl, accessTokenKey, refreshTokenKey } = config
|
||||
|
||||
function getAccessToken() {
|
||||
return localStorage.getItem(accessTokenKey)
|
||||
}
|
||||
|
||||
function getRefreshToken() {
|
||||
return localStorage.getItem(refreshTokenKey)
|
||||
}
|
||||
|
||||
function setTokens(accessToken: string, refreshToken: string) {
|
||||
localStorage.setItem(accessTokenKey, accessToken)
|
||||
localStorage.setItem(refreshTokenKey, refreshToken)
|
||||
}
|
||||
|
||||
function clearTokens() {
|
||||
localStorage.removeItem(accessTokenKey)
|
||||
localStorage.removeItem(refreshTokenKey)
|
||||
}
|
||||
|
||||
async function refreshAccessToken() {
|
||||
const refreshToken = getRefreshToken()
|
||||
if (!refreshToken) {
|
||||
return false
|
||||
}
|
||||
|
||||
const response = await fetch(`${baseUrl}/auth/refresh`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ refreshToken }),
|
||||
})
|
||||
|
||||
if (!response.ok) {
|
||||
clearTokens()
|
||||
return false
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
setTokens(data.accessToken, data.refreshToken)
|
||||
return true
|
||||
}
|
||||
|
||||
async function apiRequest<T>(path: string, options: ApiRequestOptions = {}): Promise<T> {
|
||||
const { body, auth = false, headers, ...rest } = options
|
||||
|
||||
const requestHeaders = new Headers(headers)
|
||||
|
||||
if (body !== undefined) {
|
||||
requestHeaders.set('Content-Type', 'application/json')
|
||||
}
|
||||
|
||||
if (auth) {
|
||||
const accessToken = getAccessToken()
|
||||
if (accessToken) {
|
||||
requestHeaders.set('Authorization', `Bearer ${accessToken}`)
|
||||
}
|
||||
}
|
||||
|
||||
const response = await fetch(`${baseUrl}${path}`, {
|
||||
...rest,
|
||||
headers: requestHeaders,
|
||||
body: body !== undefined ? JSON.stringify(body) : undefined,
|
||||
})
|
||||
|
||||
if (response.status === 401 && auth) {
|
||||
const refreshed = await refreshAccessToken()
|
||||
if (refreshed) {
|
||||
return apiRequest<T>(path, options)
|
||||
}
|
||||
}
|
||||
|
||||
const payload = await response.json().catch(() => null)
|
||||
|
||||
if (!response.ok) {
|
||||
throw new ApiError(
|
||||
parseErrorMessage(payload, `Request failed with status ${response.status}`),
|
||||
response.status,
|
||||
)
|
||||
}
|
||||
|
||||
return payload as T
|
||||
}
|
||||
|
||||
return {
|
||||
getAccessToken,
|
||||
getRefreshToken,
|
||||
setTokens,
|
||||
clearTokens,
|
||||
apiRequest,
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user