feat: initial admin panel (Vue + Tailwind)
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
import { api } from './client';
|
||||
|
||||
export type User = {
|
||||
id: string;
|
||||
mobile: string;
|
||||
fullName: string | null;
|
||||
role: string;
|
||||
isActive: boolean;
|
||||
};
|
||||
|
||||
export type RequestOtpResponse = {
|
||||
expiresInSeconds: number;
|
||||
otpCode?: string;
|
||||
};
|
||||
|
||||
export type VerifyOtpResponse = {
|
||||
accessToken: string;
|
||||
user: User;
|
||||
};
|
||||
|
||||
export function requestOtp(mobile: string) {
|
||||
return api<RequestOtpResponse>('/auth/request-otp', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ mobile }),
|
||||
});
|
||||
}
|
||||
|
||||
export function verifyOtp(mobile: string, code: string) {
|
||||
return api<VerifyOtpResponse>('/auth/verify-otp', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ mobile, code }),
|
||||
});
|
||||
}
|
||||
|
||||
export function fetchMe(token: string) {
|
||||
return api<User>('/auth/me', { token });
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
const baseUrl = import.meta.env.VITE_API_URL ?? 'http://localhost:3000';
|
||||
|
||||
export class ApiError extends Error {
|
||||
constructor(
|
||||
readonly status: number,
|
||||
message: string,
|
||||
) {
|
||||
super(message);
|
||||
this.name = 'ApiError';
|
||||
}
|
||||
}
|
||||
|
||||
type ApiOptions = RequestInit & {
|
||||
token?: string;
|
||||
};
|
||||
|
||||
const TOKEN_KEY = 'montime_admin_access_token';
|
||||
|
||||
export function getApiToken(): string | null {
|
||||
return localStorage.getItem(TOKEN_KEY);
|
||||
}
|
||||
|
||||
export async function api<T>(path: string, options: ApiOptions = {}): Promise<T> {
|
||||
const token = options.token ?? getApiToken() ?? undefined;
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
...(options.headers as Record<string, string> | undefined),
|
||||
};
|
||||
|
||||
if (token) {
|
||||
headers.Authorization = `Bearer ${token}`;
|
||||
}
|
||||
|
||||
const { token: _token, ...fetchOptions } = options;
|
||||
|
||||
const res = await fetch(`${baseUrl}${path}`, {
|
||||
...fetchOptions,
|
||||
headers,
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const body = (await res.json().catch(() => null)) as
|
||||
| { message?: string | string[] }
|
||||
| null;
|
||||
const msg = Array.isArray(body?.message)
|
||||
? body.message.join(', ')
|
||||
: body?.message ?? res.statusText;
|
||||
throw new ApiError(res.status, msg);
|
||||
}
|
||||
|
||||
if (res.status === 204) {
|
||||
return undefined as T;
|
||||
}
|
||||
|
||||
return res.json() as Promise<T>;
|
||||
}
|
||||
|
||||
export { TOKEN_KEY };
|
||||
Reference in New Issue
Block a user