Add LoadingSpinner and API client with auth interceptors
This commit is contained in:
parent
416b331703
commit
9f19912bdd
2 changed files with 37 additions and 0 deletions
23
src/api/client.ts
Normal file
23
src/api/client.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
export const apiClient = axios.create({
|
||||||
|
baseURL: import.meta.env.VITE_API_BASE_URL ?? '/api',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
});
|
||||||
|
|
||||||
|
apiClient.interceptors.request.use((config) => {
|
||||||
|
const token = localStorage.getItem('access_token');
|
||||||
|
if (token) config.headers.Authorization = `Bearer ${token}`;
|
||||||
|
return config;
|
||||||
|
});
|
||||||
|
|
||||||
|
apiClient.interceptors.response.use(
|
||||||
|
(r) => r,
|
||||||
|
(err) => {
|
||||||
|
if (err.response?.status === 401) {
|
||||||
|
localStorage.removeItem('access_token');
|
||||||
|
window.location.href = '/login';
|
||||||
|
}
|
||||||
|
return Promise.reject(err);
|
||||||
|
},
|
||||||
|
);
|
||||||
14
src/components/LoadingSpinner.tsx
Normal file
14
src/components/LoadingSpinner.tsx
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
interface Props { size?: number; }
|
||||||
|
|
||||||
|
export const LoadingSpinner: React.FC<Props> = ({ size = 32 }) => (
|
||||||
|
<div role="status" aria-label="Loading" style={{ width: size, height: size }}>
|
||||||
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2}>
|
||||||
|
<circle cx={12} cy={12} r={10} strokeOpacity={0.25} />
|
||||||
|
<path d="M12 2 a10 10 0 0 1 10 10" strokeLinecap="round">
|
||||||
|
<animateTransform attributeName="transform" type="rotate" from="0 12 12" to="360 12 12" dur="0.8s" repeatCount="indefinite" />
|
||||||
|
</path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
Loading…
Reference in a new issue