feat: initial project setup

- Add .NET 8 backend with Clean Architecture
- Add React + Vite + TypeScript frontend
- Implement authentication with JWT
- Implement Azure Blob Storage client
- Implement OCR integration
- Implement supplier matching service
- Implement voucher generation
- Implement Fortnox provider
- Add unit and integration tests
- Add Docker Compose configuration
This commit is contained in:
Invoice Master
2026-02-04 20:14:34 +01:00
commit 05ea67144f
250 changed files with 50402 additions and 0 deletions

45
frontend/src/api/auth.ts Normal file
View File

@@ -0,0 +1,45 @@
import apiClient from './client'
export interface LoginCredentials {
email: string
password: string
}
export interface RegisterData extends LoginCredentials {
fullName?: string
}
export interface AuthResponse {
user: {
id: string
email: string
fullName?: string
createdAt: string
}
tokens: {
accessToken: string
refreshToken: string
expiresIn: number
}
}
export const authApi = {
login: async (credentials: LoginCredentials): Promise<AuthResponse> => {
const response = await apiClient.post('/auth/login', credentials)
return response.data.data
},
register: async (data: RegisterData): Promise<AuthResponse> => {
const response = await apiClient.post('/auth/register', data)
return response.data.data
},
refresh: async (refreshToken: string) => {
const response = await apiClient.post('/auth/refresh', { refreshToken })
return response.data.data
},
logout: async () => {
await apiClient.post('/auth/logout')
},
}

View File

@@ -0,0 +1,49 @@
import axios from 'axios'
import { useAuthStore } from '../stores/authStore'
const apiClient = axios.create({
baseURL: import.meta.env.VITE_API_URL || 'http://localhost:5000/api/v1',
headers: {
'Content-Type': 'application/json',
},
})
apiClient.interceptors.request.use((config) => {
const token = useAuthStore.getState().accessToken
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
return config
})
apiClient.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config
if (error.response?.status === 401 && !originalRequest._retry) {
originalRequest._retry = true
try {
const refreshToken = useAuthStore.getState().refreshToken
const response = await axios.post(`${apiClient.defaults.baseURL}/auth/refresh`, {
refreshToken,
})
const { accessToken, refreshToken: newRefreshToken } = response.data.data.tokens
useAuthStore.getState().updateTokens(accessToken, newRefreshToken)
originalRequest.headers.Authorization = `Bearer ${accessToken}`
return apiClient(originalRequest)
} catch (refreshError) {
useAuthStore.getState().logout()
window.location.href = '/login'
return Promise.reject(refreshError)
}
}
return Promise.reject(error)
}
)
export default apiClient