Loading...
- ) : suspicious?.suspiciousEmails?.length > 0 ? (
+ ) : (suspicious?.suspiciousEmails?.length ?? 0) > 0 ? (
- {suspicious.suspiciousEmails.map((email: any, index: number) => (
+ {suspicious?.suspiciousEmails?.map((email: any, index: number) => (
{email.email}
diff --git a/src/pages/admin/settings/index.tsx b/src/pages/admin/settings/index.tsx
index 8a2a7c9..784f002 100644
--- a/src/pages/admin/settings/index.tsx
+++ b/src/pages/admin/settings/index.tsx
@@ -15,7 +15,7 @@ import {
DialogTitle,
} from "@/components/ui/dialog"
import { Plus } from "lucide-react"
-import { adminApiHelpers } from "@/lib/api-client"
+import { settingsService } from "@/services"
import { toast } from "sonner"
export default function SettingsPage() {
@@ -31,16 +31,12 @@ export default function SettingsPage() {
const { data: settings, isLoading } = useQuery({
queryKey: ['admin', 'settings', selectedCategory],
- queryFn: async () => {
- const response = await adminApiHelpers.getSettings(selectedCategory)
- return response.data
- },
+ queryFn: () => settingsService.getSettings(selectedCategory),
})
const updateSettingMutation = useMutation({
- mutationFn: async ({ key, value }: { key: string; value: string }) => {
- await adminApiHelpers.updateSetting(key, { value })
- },
+ mutationFn: ({ key, value }: { key: string; value: string }) =>
+ settingsService.updateSetting(key, { value }),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['admin', 'settings'] })
toast.success("Setting updated successfully")
@@ -51,15 +47,13 @@ export default function SettingsPage() {
})
const createSettingMutation = useMutation({
- mutationFn: async (data: {
+ mutationFn: (data: {
key: string
value: string
category: string
description?: string
isPublic?: boolean
- }) => {
- await adminApiHelpers.createSetting(data)
- },
+ }) => settingsService.createSetting(data),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['admin', 'settings'] })
toast.success("Setting created successfully")
diff --git a/src/pages/admin/users/[id]/activity.tsx b/src/pages/admin/users/[id]/activity.tsx
index 29f17b2..36e657f 100644
--- a/src/pages/admin/users/[id]/activity.tsx
+++ b/src/pages/admin/users/[id]/activity.tsx
@@ -3,7 +3,7 @@ import { useQuery } from "@tanstack/react-query"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
import { ArrowLeft } from "lucide-react"
-import { adminApiHelpers } from "@/lib/api-client"
+import { userService } from "@/services"
import { format } from "date-fns"
export default function UserActivityPage() {
@@ -12,10 +12,7 @@ export default function UserActivityPage() {
const { data: activity, isLoading } = useQuery({
queryKey: ['admin', 'users', id, 'activity'],
- queryFn: async () => {
- const response = await adminApiHelpers.getUserActivity(id!, 30)
- return response.data
- },
+ queryFn: () => userService.getUserActivity(id!, 30),
enabled: !!id,
})
diff --git a/src/pages/admin/users/[id]/index.tsx b/src/pages/admin/users/[id]/index.tsx
index 85ea85f..698798c 100644
--- a/src/pages/admin/users/[id]/index.tsx
+++ b/src/pages/admin/users/[id]/index.tsx
@@ -5,7 +5,7 @@ import { Button } from "@/components/ui/button"
import { Badge } from "@/components/ui/badge"
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"
import { ArrowLeft, Edit, Key } from "lucide-react"
-import { adminApiHelpers } from "@/lib/api-client"
+import { userService } from "@/services"
import { format } from "date-fns"
export default function UserDetailsPage() {
@@ -14,10 +14,7 @@ export default function UserDetailsPage() {
const { data: user, isLoading } = useQuery({
queryKey: ['admin', 'users', id],
- queryFn: async () => {
- const response = await adminApiHelpers.getUser(id!)
- return response.data
- },
+ queryFn: () => userService.getUser(id!),
enabled: !!id,
})
@@ -90,7 +87,9 @@ export default function UserDetailsPage() {
Updated At
-
{format(new Date(user.updatedAt), 'PPpp')}
+
+ {user.updatedAt ? format(new Date(user.updatedAt), 'PPpp') : 'N/A'}
+
diff --git a/src/pages/admin/users/index.tsx b/src/pages/admin/users/index.tsx
index 19bd804..a9b6fd0 100644
--- a/src/pages/admin/users/index.tsx
+++ b/src/pages/admin/users/index.tsx
@@ -30,7 +30,7 @@ import {
DialogTitle,
} from "@/components/ui/dialog"
import { Search, Download, Eye, UserPlus, Trash2, Key, Upload } from "lucide-react"
-import { adminApiHelpers } from "@/lib/api-client"
+import { userService } from "@/services"
import { toast } from "sonner"
import { format } from "date-fns"
@@ -55,15 +55,13 @@ export default function UsersPage() {
if (search) params.search = search
if (roleFilter !== 'all') params.role = roleFilter
if (statusFilter !== 'all') params.isActive = statusFilter === 'active'
- const response = await adminApiHelpers.getUsers(params)
- return response.data
+ return await userService.getUsers(params)
},
})
const deleteUserMutation = useMutation({
- mutationFn: async ({ id, hard }: { id: string; hard: boolean }) => {
- await adminApiHelpers.deleteUser(id, hard)
- },
+ mutationFn: ({ id, hard }: { id: string; hard: boolean }) =>
+ userService.deleteUser(id, hard),
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['admin', 'users'] })
toast.success("User deleted successfully")
@@ -75,10 +73,7 @@ export default function UsersPage() {
})
const resetPasswordMutation = useMutation({
- mutationFn: async (id: string) => {
- const response = await adminApiHelpers.resetPassword(id)
- return response.data
- },
+ mutationFn: (id: string) => userService.resetPassword(id),
onSuccess: (data) => {
toast.success(`Password reset. Temporary password: ${data.temporaryPassword}`)
setResetPasswordDialogOpen(false)
@@ -89,18 +84,12 @@ export default function UsersPage() {
})
const importUsersMutation = useMutation({
- mutationFn: async (file: File) => {
- const response = await adminApiHelpers.importUsers(file)
- return response.data
- },
+ mutationFn: (file: File) => userService.importUsers(file),
onSuccess: (data) => {
queryClient.invalidateQueries({ queryKey: ['admin', 'users'] })
- toast.success(`Imported ${data.success} users. ${data.failed} failed.`)
+ toast.success(`Imported ${data.imported} users. ${data.failed} failed.`)
setImportDialogOpen(false)
setImportFile(null)
- if (data.errors && data.errors.length > 0) {
- console.error('Import errors:', data.errors)
- }
},
onError: (error: any) => {
toast.error(error.response?.data?.message || "Failed to import users")
@@ -109,8 +98,7 @@ export default function UsersPage() {
const handleExport = async () => {
try {
- const response = await adminApiHelpers.exportUsers('csv')
- const blob = new Blob([response.data], { type: 'text/csv' })
+ const blob = await userService.exportUsers('csv')
const url = window.URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
diff --git a/src/pages/dashboard/index.tsx b/src/pages/dashboard/index.tsx
index e307905..8b72a58 100644
--- a/src/pages/dashboard/index.tsx
+++ b/src/pages/dashboard/index.tsx
@@ -1,35 +1,175 @@
+import { useQuery } from "@tanstack/react-query"
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
import { Button } from "@/components/ui/button"
-import { Download } from "lucide-react"
+import { Download, FileText, DollarSign, CreditCard, TrendingUp } from "lucide-react"
+import { dashboardService } from "@/services"
+import { toast } from "sonner"
export default function DashboardPage() {
+ const { data: profile } = useQuery({
+ queryKey: ['user', 'profile'],
+ queryFn: () => dashboardService.getUserProfile(),
+ })
+
+ const { data: stats, isLoading } = useQuery({
+ queryKey: ['user', 'stats'],
+ queryFn: () => dashboardService.getUserStats(),
+ })
+
+ const handleExport = () => {
+ toast.success("Exporting your data...")
+ }
+
+ const formatCurrency = (amount: number) => {
+ return new Intl.NumberFormat('en-US', {
+ style: 'currency',
+ currency: 'USD',
+ }).format(amount)
+ }
+
+ const getGreeting = () => {
+ const hour = new Date().getHours()
+ if (hour < 12) return 'Good Morning'
+ if (hour < 18) return 'Good Afternoon'
+ return 'Good Evening'
+ }
+
+ const userName = profile ? `${profile.firstName} ${profile.lastName}` : 'User'
+
return (
-
Good Morning, Admin
+
{getGreeting()}, {userName}
- 01 Sep - 15 Sep 2024
+ {new Date().toLocaleDateString('en-US', {
+ month: 'short',
+ day: 'numeric',
+ year: 'numeric'
+ })}
-
-
+
-
- Welcome to Dashboard
+
+ Total Invoices
+
-
- This is your main dashboard page.
-
+ {isLoading ? (
+ ...
+ ) : (
+ <>
+ {stats?.totalInvoices || 0}
+
+ {stats?.pendingInvoices || 0} pending
+
+ >
+ )}
+
+
+
+
+
+ Total Transactions
+
+
+
+ {isLoading ? (
+ ...
+ ) : (
+ <>
+ {stats?.totalTransactions || 0}
+
+ All time transactions
+
+ >
+ )}
+
+
+
+
+
+ Total Revenue
+
+
+
+ {isLoading ? (
+ ...
+ ) : (
+ <>
+
+ {formatCurrency(stats?.totalRevenue || 0)}
+
+
+ Total earnings
+
+ >
+ )}
+
+
+
+
+
+ Growth
+
+
+
+ {isLoading ? (
+ ...
+ ) : (
+ <>
+
+ {stats?.growthPercentage !== undefined
+ ? `${stats.growthPercentage > 0 ? '+' : ''}${stats.growthPercentage.toFixed(1)}%`
+ : 'N/A'
+ }
+
+
+ vs last month
+
+ >
+ )}
+
+ {stats?.recentActivity && stats.recentActivity.length > 0 && (
+
+
+ Recent Activity
+
+
+
+ {stats.recentActivity.map((activity) => (
+
+
+
{activity.description}
+
+ {new Date(activity.date).toLocaleDateString('en-US', {
+ month: 'short',
+ day: 'numeric',
+ year: 'numeric',
+ })}
+
+
+ {activity.amount && (
+
+
{formatCurrency(activity.amount)}
+
+ )}
+
+ ))}
+
+
+
+ )}
)
}
diff --git a/src/pages/login/__tests__/index.test.tsx b/src/pages/login/__tests__/index.test.tsx
index 7248b64..3c0afb2 100644
--- a/src/pages/login/__tests__/index.test.tsx
+++ b/src/pages/login/__tests__/index.test.tsx
@@ -2,11 +2,11 @@ import { describe, it, expect, vi } from 'vitest'
import { render, screen, waitFor } from '@/test/test-utils'
import userEvent from '@testing-library/user-event'
import LoginPage from '../index'
-import { adminApiHelpers } from '@/lib/api-client'
+import { authService } from '@/services'
-// Mock the API client
-vi.mock('@/lib/api-client', () => ({
- adminApiHelpers: {
+// Mock the service layer
+vi.mock('@/services', () => ({
+ authService: {
login: vi.fn(),
},
}))
@@ -52,20 +52,19 @@ describe('LoginPage', () => {
it('should handle form submission', async () => {
const user = userEvent.setup()
- const mockLogin = vi.mocked(adminApiHelpers.login)
+ const mockLogin = vi.mocked(authService.login)
mockLogin.mockResolvedValue({
- data: {
- access_token: 'fake-token',
- user: {
- id: '1',
- email: 'admin@example.com',
- role: 'ADMIN',
- firstName: 'Admin',
- lastName: 'User',
- },
+ accessToken: 'fake-token',
+ refreshToken: 'fake-refresh-token',
+ user: {
+ id: '1',
+ email: 'admin@example.com',
+ role: 'ADMIN',
+ firstName: 'Admin',
+ lastName: 'User',
},
- } as any)
+ })
render(
)
@@ -87,18 +86,19 @@ describe('LoginPage', () => {
it('should show error for non-admin users', async () => {
const user = userEvent.setup()
- const mockLogin = vi.mocked(adminApiHelpers.login)
+ const mockLogin = vi.mocked(authService.login)
mockLogin.mockResolvedValue({
- data: {
- access_token: 'fake-token',
- user: {
- id: '1',
- email: 'user@example.com',
- role: 'USER', // Not ADMIN
- },
+ accessToken: 'fake-token',
+ refreshToken: 'fake-refresh-token',
+ user: {
+ id: '1',
+ email: 'user@example.com',
+ firstName: 'User',
+ lastName: 'Test',
+ role: 'USER', // Not ADMIN
},
- } as any)
+ })
render(
)
diff --git a/src/pages/login/index.tsx b/src/pages/login/index.tsx
index 3d3f5bb..27db5d1 100644
--- a/src/pages/login/index.tsx
+++ b/src/pages/login/index.tsx
@@ -6,7 +6,7 @@ import { Input } from "@/components/ui/input"
import { Label } from "@/components/ui/label"
import { Eye, EyeOff } from "lucide-react"
import { toast } from "sonner"
-import { adminApiHelpers } from "@/lib/api-client"
+import { authService } from "@/services"
import { errorTracker } from "@/lib/error-tracker"
export default function LoginPage() {
@@ -24,58 +24,37 @@ export default function LoginPage() {
setIsLoading(true)
try {
- const response = await adminApiHelpers.login({ email, password })
- console.log('Login response:', response.data) // Debug log
+ const response = await authService.login({ email, password })
- // Handle different response formats
- const responseData = response.data
- const access_token = responseData.access_token || responseData.token || responseData.accessToken
- const refresh_token = responseData.refresh_token || responseData.refreshToken
- const user = responseData.user || responseData.data?.user || responseData
-
- console.log('Extracted token:', access_token) // Debug log
- console.log('Extracted user:', user) // Debug log
-
// Check if user is admin
- if (user.role !== 'ADMIN') {
+ if (response.user.role !== 'ADMIN') {
toast.error("Access denied. Admin privileges required.")
setIsLoading(false)
return
}
- // Store tokens and user data
- if (access_token) {
- localStorage.setItem('access_token', access_token)
- console.log('Access token stored in localStorage') // Debug log
- } else {
- console.warn('No access_token in response - assuming httpOnly cookies') // Debug log
- }
-
- if (refresh_token) {
- localStorage.setItem('refresh_token', refresh_token)
- console.log('Refresh token stored in localStorage') // Debug log
- }
-
- localStorage.setItem('user', JSON.stringify(user))
- console.log('User stored in localStorage') // Debug log
+ // Set user context for error tracking
+ errorTracker.setUser({
+ id: response.user.id,
+ email: response.user.email,
+ name: `${response.user.firstName} ${response.user.lastName}`,
+ })
// Show success message
toast.success("Login successful!")
- // Small delay to ensure localStorage is persisted
- await new Promise(resolve => setTimeout(resolve, 100))
-
- // Verify token is stored before navigation
- const storedToken = localStorage.getItem('access_token')
- console.log('Token verification before navigation:', storedToken) // Debug log
-
// Navigate to dashboard
- console.log('Navigating to:', from) // Debug log
navigate(from, { replace: true })
} catch (error: any) {
- console.error('Login error:', error) // Debug log
+ console.error('Login error:', error)
const message = error.response?.data?.message || "Invalid email or password"
toast.error(message)
+
+ // Track login error
+ errorTracker.trackError(error, {
+ extra: { email, action: 'login' }
+ })
+
setIsLoading(false)
}
}
diff --git a/src/services/analytics.service.ts b/src/services/analytics.service.ts
new file mode 100644
index 0000000..243c7e5
--- /dev/null
+++ b/src/services/analytics.service.ts
@@ -0,0 +1,107 @@
+import apiClient from './api/client'
+
+export interface OverviewStats {
+ users?: {
+ total: number
+ active: number
+ inactive: number
+ }
+ invoices?: {
+ total: number
+ }
+ revenue?: {
+ total: number
+ }
+ storage?: {
+ totalSize: number
+ documents: number
+ }
+ totalUsers: number
+ activeUsers: number
+ totalRevenue: number
+ totalTransactions: number
+ storageUsed: number
+ storageLimit: number
+}
+
+export interface UserGrowthData {
+ date: string
+ users: number
+ activeUsers: number
+}
+
+export interface RevenueData {
+ date: string
+ revenue: number
+ transactions: number
+}
+
+class AnalyticsService {
+ /**
+ * Get overview statistics
+ */
+ async getOverview(): Promise
{
+ const response = await apiClient.get('/admin/analytics/overview')
+ return response.data
+ }
+
+ /**
+ * Get user growth data
+ */
+ async getUserGrowth(days: number = 30): Promise {
+ const response = await apiClient.get('/admin/analytics/users/growth', {
+ params: { days },
+ })
+ return response.data
+ }
+
+ /**
+ * Get revenue data
+ */
+ async getRevenue(period: '7days' | '30days' | '90days' = '30days'): Promise {
+ const response = await apiClient.get('/admin/analytics/revenue', {
+ params: { period },
+ })
+ return response.data
+ }
+
+ /**
+ * Get API usage statistics
+ */
+ async getApiUsage(days: number = 7): Promise {
+ const response = await apiClient.get('/admin/analytics/api-usage', {
+ params: { days },
+ })
+ return response.data
+ }
+
+ /**
+ * Get error rate statistics
+ */
+ async getErrorRate(days: number = 7): Promise {
+ const response = await apiClient.get('/admin/analytics/error-rate', {
+ params: { days },
+ })
+ return response.data
+ }
+
+ /**
+ * Get storage usage by user
+ */
+ async getStorageByUser(limit: number = 10): Promise {
+ const response = await apiClient.get('/admin/analytics/storage/by-user', {
+ params: { limit },
+ })
+ return response.data
+ }
+
+ /**
+ * Get storage analytics
+ */
+ async getStorageAnalytics(): Promise {
+ const response = await apiClient.get('/admin/analytics/storage')
+ return response.data
+ }
+}
+
+export const analyticsService = new AnalyticsService()
diff --git a/src/services/announcement.service.ts b/src/services/announcement.service.ts
new file mode 100644
index 0000000..796adcd
--- /dev/null
+++ b/src/services/announcement.service.ts
@@ -0,0 +1,88 @@
+import apiClient from './api/client'
+
+export interface Announcement {
+ id: string
+ title: string
+ message: string
+ type: 'info' | 'warning' | 'success' | 'error'
+ priority: number
+ targetAudience: string
+ isActive: boolean
+ startsAt?: string
+ endsAt?: string
+ createdAt: string
+ updatedAt: string
+}
+
+export interface CreateAnnouncementData {
+ title: string
+ message: string
+ type?: 'info' | 'warning' | 'success' | 'error'
+ priority?: number
+ targetAudience?: string
+ startsAt?: string
+ endsAt?: string
+}
+
+export interface UpdateAnnouncementData {
+ title?: string
+ message?: string
+ type?: 'info' | 'warning' | 'success' | 'error'
+ priority?: number
+ targetAudience?: string
+ startsAt?: string
+ endsAt?: string
+}
+
+class AnnouncementService {
+ /**
+ * Get all announcements
+ */
+ async getAnnouncements(activeOnly: boolean = false): Promise {
+ const response = await apiClient.get('/admin/announcements', {
+ params: { activeOnly },
+ })
+ return response.data
+ }
+
+ /**
+ * Get single announcement by ID
+ */
+ async getAnnouncement(id: string): Promise {
+ const response = await apiClient.get(`/admin/announcements/${id}`)
+ return response.data
+ }
+
+ /**
+ * Create new announcement
+ */
+ async createAnnouncement(data: CreateAnnouncementData): Promise {
+ const response = await apiClient.post('/admin/announcements', data)
+ return response.data
+ }
+
+ /**
+ * Update announcement
+ */
+ async updateAnnouncement(id: string, data: UpdateAnnouncementData): Promise {
+ const response = await apiClient.put(`/admin/announcements/${id}`, data)
+ return response.data
+ }
+
+ /**
+ * Toggle announcement active status
+ */
+ async toggleAnnouncement(id: string): Promise {
+ const response = await apiClient.patch(`/admin/announcements/${id}/toggle`)
+ return response.data
+ }
+
+ /**
+ * Delete announcement
+ */
+ async deleteAnnouncement(id: string): Promise {
+ await apiClient.delete(`/admin/announcements/${id}`)
+ }
+}
+
+export const announcementService = new AnnouncementService()
diff --git a/src/services/api/client.ts b/src/services/api/client.ts
new file mode 100644
index 0000000..e1c2971
--- /dev/null
+++ b/src/services/api/client.ts
@@ -0,0 +1,71 @@
+import axios, { type AxiosInstance, type AxiosError } from 'axios'
+
+const API_BASE_URL = import.meta.env.VITE_BACKEND_API_URL || 'http://localhost:3001/api/v1'
+
+// Create axios instance with default config
+const apiClient: AxiosInstance = axios.create({
+ baseURL: API_BASE_URL,
+ headers: {
+ 'Content-Type': 'application/json',
+ },
+ withCredentials: true, // Send cookies with requests
+ timeout: 30000, // 30 second timeout
+})
+
+// Request interceptor - Add auth token
+apiClient.interceptors.request.use(
+ (config) => {
+ // Add token from localStorage as fallback (cookies are preferred)
+ const token = localStorage.getItem('access_token')
+ if (token) {
+ config.headers.Authorization = `Bearer ${token}`
+ }
+ return config
+ },
+ (error) => {
+ return Promise.reject(error)
+ }
+)
+
+// Response interceptor - Handle errors and token refresh
+apiClient.interceptors.response.use(
+ (response) => response,
+ async (error: AxiosError) => {
+ const originalRequest = error.config as any
+
+ // Handle 401 Unauthorized - Try to refresh token
+ if (error.response?.status === 401 && !originalRequest._retry) {
+ originalRequest._retry = true
+
+ try {
+ // Try to refresh token
+ const refreshToken = localStorage.getItem('refresh_token')
+ if (refreshToken) {
+ const response = await axios.post(
+ `${API_BASE_URL}/auth/refresh`,
+ { refreshToken },
+ { withCredentials: true }
+ )
+
+ const { accessToken } = response.data
+ localStorage.setItem('access_token', accessToken)
+
+ // Retry original request with new token
+ originalRequest.headers.Authorization = `Bearer ${accessToken}`
+ return apiClient(originalRequest)
+ }
+ } catch (refreshError) {
+ // Refresh failed - logout user
+ localStorage.removeItem('access_token')
+ localStorage.removeItem('refresh_token')
+ localStorage.removeItem('user')
+ window.location.href = '/login'
+ return Promise.reject(refreshError)
+ }
+ }
+
+ return Promise.reject(error)
+ }
+)
+
+export default apiClient
diff --git a/src/services/audit.service.ts b/src/services/audit.service.ts
new file mode 100644
index 0000000..e08d122
--- /dev/null
+++ b/src/services/audit.service.ts
@@ -0,0 +1,101 @@
+import apiClient from './api/client'
+
+export interface AuditLog {
+ id: string
+ userId: string
+ action: string
+ resourceType: string
+ resourceId: string
+ changes?: Record
+ ipAddress: string
+ userAgent: string
+ timestamp: string
+}
+
+export interface GetAuditLogsParams {
+ page?: number
+ limit?: number
+ userId?: string
+ action?: string
+ resourceType?: string
+ resourceId?: string
+ startDate?: string
+ endDate?: string
+ search?: string
+}
+
+export interface AuditStats {
+ totalActions: number
+ uniqueUsers: number
+ topActions: Array<{ action: string; count: number }>
+ topUsers: Array<{ userId: string; count: number }>
+}
+
+class AuditService {
+ /**
+ * Get audit logs with pagination and filters
+ */
+ async getAuditLogs(params?: GetAuditLogsParams): Promise<{
+ data: AuditLog[]
+ total: number
+ page: number
+ limit: number
+ totalPages: number
+ }> {
+ const response = await apiClient.get('/admin/audit/logs', { params })
+ return response.data
+ }
+
+ /**
+ * Get audit log by ID
+ */
+ async getAuditLog(id: string): Promise {
+ const response = await apiClient.get(`/admin/audit/logs/${id}`)
+ return response.data
+ }
+
+ /**
+ * Get user audit activity
+ */
+ async getUserAuditActivity(userId: string, days: number = 30): Promise {
+ const response = await apiClient.get(`/admin/audit/users/${userId}`, {
+ params: { days },
+ })
+ return response.data
+ }
+
+ /**
+ * Get resource history
+ */
+ async getResourceHistory(type: string, id: string): Promise {
+ const response = await apiClient.get(`/admin/audit/resource/${type}/${id}`)
+ return response.data
+ }
+
+ /**
+ * Get audit statistics
+ */
+ async getAuditStats(startDate?: string, endDate?: string): Promise {
+ const response = await apiClient.get('/admin/audit/stats', {
+ params: { startDate, endDate },
+ })
+ return response.data
+ }
+
+ /**
+ * Export audit logs
+ */
+ async exportAuditLogs(params?: {
+ format?: 'csv' | 'json'
+ startDate?: string
+ endDate?: string
+ }): Promise {
+ const response = await apiClient.get('/admin/audit/export', {
+ params,
+ responseType: 'blob',
+ })
+ return response.data
+ }
+}
+
+export const auditService = new AuditService()
diff --git a/src/services/auth.service.ts b/src/services/auth.service.ts
new file mode 100644
index 0000000..55d45df
--- /dev/null
+++ b/src/services/auth.service.ts
@@ -0,0 +1,99 @@
+import apiClient from './api/client'
+
+export interface LoginRequest {
+ email: string
+ password: string
+}
+
+export interface LoginResponse {
+ accessToken: string
+ refreshToken: string
+ user: {
+ id: string
+ email: string
+ firstName: string
+ lastName: string
+ role: string
+ }
+}
+
+export interface RefreshTokenResponse {
+ accessToken: string
+}
+
+class AuthService {
+ /**
+ * Login user with email and password
+ */
+ async login(credentials: LoginRequest): Promise {
+ const response = await apiClient.post('/auth/login', credentials)
+
+ // Store tokens
+ if (response.data.accessToken) {
+ localStorage.setItem('access_token', response.data.accessToken)
+ }
+ if (response.data.refreshToken) {
+ localStorage.setItem('refresh_token', response.data.refreshToken)
+ }
+ if (response.data.user) {
+ localStorage.setItem('user', JSON.stringify(response.data.user))
+ }
+
+ return response.data
+ }
+
+ /**
+ * Logout user
+ */
+ async logout(): Promise {
+ try {
+ await apiClient.post('/auth/logout')
+ } finally {
+ // Clear local storage even if API call fails
+ localStorage.removeItem('access_token')
+ localStorage.removeItem('refresh_token')
+ localStorage.removeItem('user')
+ }
+ }
+
+ /**
+ * Refresh access token
+ */
+ async refreshToken(): Promise {
+ const refreshToken = localStorage.getItem('refresh_token')
+ const response = await apiClient.post('/auth/refresh', {
+ refreshToken,
+ })
+
+ if (response.data.accessToken) {
+ localStorage.setItem('access_token', response.data.accessToken)
+ }
+
+ return response.data
+ }
+
+ /**
+ * Get current user from localStorage
+ */
+ getCurrentUser() {
+ const userStr = localStorage.getItem('user')
+ return userStr ? JSON.parse(userStr) : null
+ }
+
+ /**
+ * Check if user is authenticated
+ */
+ isAuthenticated(): boolean {
+ return !!localStorage.getItem('access_token')
+ }
+
+ /**
+ * Check if user is admin
+ */
+ isAdmin(): boolean {
+ const user = this.getCurrentUser()
+ return user?.role === 'ADMIN'
+ }
+}
+
+export const authService = new AuthService()
diff --git a/src/services/dashboard.service.ts b/src/services/dashboard.service.ts
new file mode 100644
index 0000000..c652e79
--- /dev/null
+++ b/src/services/dashboard.service.ts
@@ -0,0 +1,54 @@
+import apiClient from './api/client'
+
+export interface UserDashboardStats {
+ totalInvoices: number
+ totalTransactions: number
+ totalRevenue: number
+ pendingInvoices: number
+ growthPercentage?: number
+ recentActivity?: Array<{
+ id: string
+ type: string
+ description: string
+ date: string
+ amount?: number
+ }>
+}
+
+export interface UserProfile {
+ id: string
+ email: string
+ firstName: string
+ lastName: string
+ role: string
+}
+
+class DashboardService {
+ /**
+ * Get current user profile
+ */
+ async getUserProfile(): Promise {
+ const response = await apiClient.get('/user/profile')
+ return response.data
+ }
+
+ /**
+ * Get user dashboard statistics
+ */
+ async getUserStats(): Promise {
+ const response = await apiClient.get('/user/stats')
+ return response.data
+ }
+
+ /**
+ * Get user recent activity
+ */
+ async getRecentActivity(limit: number = 10): Promise {
+ const response = await apiClient.get('/user/activity', {
+ params: { limit },
+ })
+ return response.data
+ }
+}
+
+export const dashboardService = new DashboardService()
diff --git a/src/services/index.ts b/src/services/index.ts
new file mode 100644
index 0000000..c07ede8
--- /dev/null
+++ b/src/services/index.ts
@@ -0,0 +1,21 @@
+// Export all services from a single entry point
+export { authService } from './auth.service'
+export { userService } from './user.service'
+export { analyticsService } from './analytics.service'
+export { securityService } from './security.service'
+export { systemService } from './system.service'
+export { announcementService } from './announcement.service'
+export { auditService } from './audit.service'
+export { settingsService } from './settings.service'
+export { dashboardService } from './dashboard.service'
+
+// Export types
+export type { LoginRequest, LoginResponse } from './auth.service'
+export type { User, GetUsersParams, PaginatedResponse } from './user.service'
+export type { OverviewStats, UserGrowthData, RevenueData } from './analytics.service'
+export type { SuspiciousActivity, ActiveSession, FailedLogin, ApiKey } from './security.service'
+export type { HealthStatus, SystemInfo, MaintenanceStatus } from './system.service'
+export type { Announcement, CreateAnnouncementData, UpdateAnnouncementData } from './announcement.service'
+export type { AuditLog, GetAuditLogsParams, AuditStats } from './audit.service'
+export type { Setting, CreateSettingData, UpdateSettingData } from './settings.service'
+export type { UserDashboardStats, UserProfile } from './dashboard.service'
diff --git a/src/services/security.service.ts b/src/services/security.service.ts
new file mode 100644
index 0000000..27f72e3
--- /dev/null
+++ b/src/services/security.service.ts
@@ -0,0 +1,112 @@
+import apiClient from './api/client'
+
+export interface SuspiciousActivity {
+ id: string
+ userId: string
+ type: string
+ description: string
+ ipAddress: string
+ timestamp: string
+ severity: 'low' | 'medium' | 'high'
+}
+
+export interface ActiveSession {
+ id: string
+ userId: string
+ ipAddress: string
+ userAgent: string
+ lastActivity: string
+ createdAt: string
+}
+
+export interface FailedLogin {
+ id: string
+ email: string
+ ipAddress: string
+ timestamp: string
+ reason: string
+}
+
+export interface ApiKey {
+ id: string
+ name: string
+ key: string
+ userId: string
+ createdAt: string
+ lastUsed?: string
+ isActive: boolean
+}
+
+class SecurityService {
+ /**
+ * Get suspicious activity logs
+ */
+ async getSuspiciousActivity(): Promise<{
+ suspiciousIPs?: any[]
+ suspiciousEmails?: any[]
+ }> {
+ const response = await apiClient.get('/admin/security/suspicious')
+ return response.data
+ }
+
+ /**
+ * Get active user sessions
+ */
+ async getActiveSessions(): Promise {
+ const response = await apiClient.get('/admin/security/sessions')
+ return response.data
+ }
+
+ /**
+ * Terminate a user session
+ */
+ async terminateSession(sessionId: string): Promise {
+ await apiClient.delete(`/admin/security/sessions/${sessionId}`)
+ }
+
+ /**
+ * Get failed login attempts
+ */
+ async getFailedLogins(params?: {
+ page?: number
+ limit?: number
+ email?: string
+ }): Promise<{ data: FailedLogin[]; total: number }> {
+ const response = await apiClient.get('/admin/security/failed-logins', { params })
+ return response.data
+ }
+
+ /**
+ * Get rate limit violations
+ */
+ async getRateLimitViolations(days: number = 7): Promise {
+ const response = await apiClient.get('/admin/security/rate-limits', {
+ params: { days },
+ })
+ return response.data
+ }
+
+ /**
+ * Get all API keys
+ */
+ async getAllApiKeys(): Promise {
+ const response = await apiClient.get('/admin/security/api-keys')
+ return response.data
+ }
+
+ /**
+ * Revoke an API key
+ */
+ async revokeApiKey(id: string): Promise {
+ await apiClient.delete(`/admin/security/api-keys/${id}`)
+ }
+
+ /**
+ * Ban an IP address
+ */
+ async banIpAddress(ipAddress: string, reason: string): Promise {
+ await apiClient.post('/admin/security/ban-ip', { ipAddress, reason })
+ }
+}
+
+export const securityService = new SecurityService()
diff --git a/src/services/settings.service.ts b/src/services/settings.service.ts
new file mode 100644
index 0000000..3b5a77d
--- /dev/null
+++ b/src/services/settings.service.ts
@@ -0,0 +1,78 @@
+import apiClient from './api/client'
+
+export interface Setting {
+ key: string
+ value: string
+ category: string
+ description?: string
+ isPublic: boolean
+ createdAt: string
+ updatedAt: string
+}
+
+export interface CreateSettingData {
+ key: string
+ value: string
+ category: string
+ description?: string
+ isPublic?: boolean
+}
+
+export interface UpdateSettingData {
+ value: string
+ description?: string
+ isPublic?: boolean
+}
+
+class SettingsService {
+ /**
+ * Get all settings, optionally filtered by category
+ */
+ async getSettings(category?: string): Promise {
+ const response = await apiClient.get('/admin/system/settings', {
+ params: { category },
+ })
+ return response.data
+ }
+
+ /**
+ * Get single setting by key
+ */
+ async getSetting(key: string): Promise {
+ const response = await apiClient.get(`/admin/system/settings/${key}`)
+ return response.data
+ }
+
+ /**
+ * Create new setting
+ */
+ async createSetting(data: CreateSettingData): Promise {
+ const response = await apiClient.post('/admin/system/settings', data)
+ return response.data
+ }
+
+ /**
+ * Update setting
+ */
+ async updateSetting(key: string, data: UpdateSettingData): Promise {
+ const response = await apiClient.put(`/admin/system/settings/${key}`, data)
+ return response.data
+ }
+
+ /**
+ * Delete setting
+ */
+ async deleteSetting(key: string): Promise {
+ await apiClient.delete(`/admin/system/settings/${key}`)
+ }
+
+ /**
+ * Get public settings (for frontend use)
+ */
+ async getPublicSettings(): Promise> {
+ const response = await apiClient.get>('/settings/public')
+ return response.data
+ }
+}
+
+export const settingsService = new SettingsService()
diff --git a/src/services/system.service.ts b/src/services/system.service.ts
new file mode 100644
index 0000000..a088ccb
--- /dev/null
+++ b/src/services/system.service.ts
@@ -0,0 +1,94 @@
+import apiClient from './api/client'
+
+export interface HealthStatus {
+ status: 'healthy' | 'degraded' | 'down'
+ database: 'connected' | 'disconnected'
+ redis: 'connected' | 'disconnected'
+ uptime: number
+ timestamp: string
+ recentErrors?: number
+ activeUsers?: number
+}
+
+export interface SystemInfo {
+ version: string
+ environment: string
+ nodeVersion: string
+ memory: {
+ used: number
+ total: number
+ }
+ cpu: {
+ usage: number
+ cores: number
+ loadAverage?: number[]
+ }
+ platform?: string
+ architecture?: string
+ uptime?: number
+ env?: string
+}
+
+export interface MaintenanceStatus {
+ enabled: boolean
+ message?: string
+ scheduledStart?: string
+ scheduledEnd?: string
+}
+
+class SystemService {
+ /**
+ * Get system health status
+ */
+ async getHealth(): Promise {
+ const response = await apiClient.get('/admin/system/health')
+ return response.data
+ }
+
+ /**
+ * Get system information
+ */
+ async getSystemInfo(): Promise {
+ const response = await apiClient.get('/admin/system/info')
+ return response.data
+ }
+
+ /**
+ * Get maintenance mode status
+ */
+ async getMaintenanceStatus(): Promise {
+ const response = await apiClient.get('/admin/maintenance/status')
+ return response.data
+ }
+
+ /**
+ * Enable maintenance mode
+ */
+ async enableMaintenance(message?: string): Promise {
+ await apiClient.post('/admin/maintenance/enable', { message })
+ }
+
+ /**
+ * Disable maintenance mode
+ */
+ async disableMaintenance(): Promise {
+ await apiClient.post('/admin/maintenance/disable')
+ }
+
+ /**
+ * Clear application cache
+ */
+ async clearCache(): Promise {
+ await apiClient.post('/admin/system/clear-cache')
+ }
+
+ /**
+ * Run database migrations
+ */
+ async runMigrations(): Promise<{ success: boolean; message: string }> {
+ const response = await apiClient.post('/admin/system/migrate')
+ return response.data
+ }
+}
+
+export const systemService = new SystemService()
diff --git a/src/services/user.service.ts b/src/services/user.service.ts
new file mode 100644
index 0000000..7d4998b
--- /dev/null
+++ b/src/services/user.service.ts
@@ -0,0 +1,132 @@
+import apiClient from './api/client'
+
+export interface User {
+ id: string
+ email: string
+ firstName: string
+ lastName: string
+ role: string
+ isActive: boolean
+ createdAt: string
+ updatedAt?: string
+ lastLogin?: string
+ _count?: {
+ invoices?: number
+ transactions?: number
+ documents?: number
+ activityLogs?: number
+ reports?: number
+ payments?: number
+ }
+}
+
+export interface GetUsersParams {
+ page?: number
+ limit?: number
+ search?: string
+ role?: string
+ isActive?: boolean
+}
+
+export interface PaginatedResponse {
+ data: T[]
+ total: number
+ page: number
+ limit: number
+ totalPages: number
+}
+
+class UserService {
+ /**
+ * Get paginated list of users
+ */
+ async getUsers(params?: GetUsersParams): Promise> {
+ const response = await apiClient.get>('/admin/users', { params })
+ return response.data
+ }
+
+ /**
+ * Get single user by ID
+ */
+ async getUser(id: string): Promise {
+ const response = await apiClient.get(`/admin/users/${id}`)
+ return response.data
+ }
+
+ /**
+ * Create new user
+ */
+ async createUser(data: Partial): Promise {
+ const response = await apiClient.post('/admin/users', data)
+ return response.data
+ }
+
+ /**
+ * Update user
+ */
+ async updateUser(id: string, data: Partial): Promise {
+ const response = await apiClient.patch(`/admin/users/${id}`, data)
+ return response.data
+ }
+
+ /**
+ * Delete user (soft or hard delete)
+ */
+ async deleteUser(id: string, hard: boolean = false): Promise {
+ await apiClient.delete(`/admin/users/${id}`, {
+ params: { hard },
+ })
+ }
+
+ /**
+ * Reset user password
+ */
+ async resetPassword(id: string): Promise<{ temporaryPassword: string }> {
+ const response = await apiClient.post<{ temporaryPassword: string }>(
+ `/admin/users/${id}/reset-password`
+ )
+ return response.data
+ }
+
+ /**
+ * Get user activity logs
+ */
+ async getUserActivity(id: string, days: number = 30): Promise {
+ const response = await apiClient.get(`/admin/users/${id}/activity`, {
+ params: { days },
+ })
+ return response.data
+ }
+
+ /**
+ * Import users from CSV
+ */
+ async importUsers(file: File): Promise<{ imported: number; failed: number }> {
+ const formData = new FormData()
+ formData.append('file', file)
+
+ const response = await apiClient.post<{ imported: number; failed: number }>(
+ '/admin/users/import',
+ formData,
+ {
+ headers: {
+ 'Content-Type': 'multipart/form-data',
+ },
+ }
+ )
+ return response.data
+ }
+
+ /**
+ * Export users to CSV
+ */
+ async exportUsers(format: 'csv' | 'json' = 'csv'): Promise {
+ const response = await apiClient.get('/admin/users/export', {
+ params: { format },
+ responseType: 'blob',
+ })
+ return response.data
+ }
+}
+
+export const userService = new UserService()