Files
honeyDueAPI/admin/src/lib/api.ts
Trey t af87bd943e Add clear stuck jobs admin feature and simplify worker scheduling
- Add POST /api/admin/settings/clear-stuck-jobs endpoint to clear
  stuck/failed asynq worker jobs from Redis (retry queue, archived,
  orphaned task metadata)
- Add "Clear Stuck Jobs" button to admin settings UI
- Remove TASK_REMINDER_MINUTE config - all jobs now run at minute 0
- Simplify formatCron to only take hour parameter
- Update default notification times to CST-friendly hours

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-06 23:05:48 -06:00

1248 lines
34 KiB
TypeScript

import axios, { AxiosError, type AxiosInstance } from 'axios';
import type {
Notification,
NotificationDetail,
NotificationListParams,
NotificationStats,
Subscription,
SubscriptionDetail,
SubscriptionListParams,
UpdateSubscriptionRequest,
SubscriptionStats,
DashboardStats,
} from '@/types/models';
// In production, API is on same origin. In dev, use env var or localhost
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || '';
// Create axios instance
const api: AxiosInstance = axios.create({
baseURL: `${API_BASE_URL}/api/admin`,
headers: {
'Content-Type': 'application/json',
},
});
// Add auth token to requests
api.interceptors.request.use((config) => {
if (typeof window !== 'undefined') {
const token = localStorage.getItem('admin_token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
}
return config;
});
// Handle auth errors
api.interceptors.response.use(
(response) => response,
(error: AxiosError) => {
if (error.response?.status === 401) {
if (typeof window !== 'undefined') {
localStorage.removeItem('admin_token');
window.location.href = '/admin/login/';
}
}
return Promise.reject(error);
}
);
// Types
export interface AdminUser {
id: number;
email: string;
first_name: string;
last_name: string;
role: 'super_admin' | 'admin' | 'moderator';
is_active: boolean;
last_login?: string;
created_at: string;
}
export interface LoginRequest {
email: string;
password: string;
}
export interface LoginResponse {
token: string;
admin: AdminUser;
}
export interface ApiError {
error: string;
}
// Auth API
export const authApi = {
login: async (data: LoginRequest): Promise<LoginResponse> => {
const response = await api.post<LoginResponse>('/auth/login', data);
return response.data;
},
logout: async (): Promise<void> => {
await api.post('/auth/logout');
},
me: async (): Promise<AdminUser> => {
const response = await api.get<AdminUser>('/auth/me');
return response.data;
},
refreshToken: async (): Promise<{ token: string }> => {
const response = await api.post<{ token: string }>('/auth/refresh');
return response.data;
},
};
// Types for User management
import type {
PaginatedResponse,
User,
UserDetail,
UserListParams,
CreateUserRequest,
UpdateUserRequest,
Residence,
ResidenceDetail,
ResidenceListParams,
CreateResidenceRequest,
UpdateResidenceRequest,
Task,
TaskDetail,
TaskListParams,
CreateTaskRequest,
UpdateTaskRequest,
Contractor,
ContractorDetail,
ContractorListParams,
CreateContractorRequest,
UpdateContractorRequest,
Document,
DocumentDetail,
DocumentListParams,
CreateDocumentRequest,
UpdateDocumentRequest,
} from '@/types/models';
// Users API
export const usersApi = {
list: async (params?: UserListParams): Promise<PaginatedResponse<User>> => {
const response = await api.get<PaginatedResponse<User>>('/users', { params });
return response.data;
},
get: async (id: number): Promise<UserDetail> => {
const response = await api.get<UserDetail>(`/users/${id}`);
return response.data;
},
create: async (data: CreateUserRequest): Promise<User> => {
const response = await api.post<User>('/users', data);
return response.data;
},
update: async (id: number, data: UpdateUserRequest): Promise<User> => {
const response = await api.put<User>(`/users/${id}`, data);
return response.data;
},
delete: async (id: number): Promise<void> => {
await api.delete(`/users/${id}`);
},
bulkDelete: async (ids: number[]): Promise<void> => {
await api.delete('/users/bulk', { data: { ids } });
},
};
// Residences API
export const residencesApi = {
list: async (params?: ResidenceListParams): Promise<PaginatedResponse<Residence>> => {
const response = await api.get<PaginatedResponse<Residence>>('/residences', { params });
return response.data;
},
get: async (id: number): Promise<ResidenceDetail> => {
const response = await api.get<ResidenceDetail>(`/residences/${id}`);
return response.data;
},
create: async (data: CreateResidenceRequest): Promise<Residence> => {
const response = await api.post<Residence>('/residences', data);
return response.data;
},
update: async (id: number, data: UpdateResidenceRequest): Promise<Residence> => {
const response = await api.put<Residence>(`/residences/${id}`, data);
return response.data;
},
delete: async (id: number): Promise<void> => {
await api.delete(`/residences/${id}`);
},
bulkDelete: async (ids: number[]): Promise<void> => {
await api.delete('/residences/bulk', { data: { ids } });
},
};
// Tasks API
export const tasksApi = {
list: async (params?: TaskListParams): Promise<PaginatedResponse<Task>> => {
const response = await api.get<PaginatedResponse<Task>>('/tasks', { params });
return response.data;
},
get: async (id: number): Promise<TaskDetail> => {
const response = await api.get<TaskDetail>(`/tasks/${id}`);
return response.data;
},
create: async (data: CreateTaskRequest): Promise<Task> => {
const response = await api.post<Task>('/tasks', data);
return response.data;
},
update: async (id: number, data: UpdateTaskRequest): Promise<Task> => {
const response = await api.put<Task>(`/tasks/${id}`, data);
return response.data;
},
delete: async (id: number): Promise<void> => {
await api.delete(`/tasks/${id}`);
},
bulkDelete: async (ids: number[]): Promise<void> => {
await api.delete('/tasks/bulk', { data: { ids } });
},
};
// Contractors API
export const contractorsApi = {
list: async (params?: ContractorListParams): Promise<PaginatedResponse<Contractor>> => {
const response = await api.get<PaginatedResponse<Contractor>>('/contractors', { params });
return response.data;
},
get: async (id: number): Promise<ContractorDetail> => {
const response = await api.get<ContractorDetail>(`/contractors/${id}`);
return response.data;
},
create: async (data: CreateContractorRequest): Promise<Contractor> => {
const response = await api.post<Contractor>('/contractors', data);
return response.data;
},
update: async (id: number, data: UpdateContractorRequest): Promise<Contractor> => {
const response = await api.put<Contractor>(`/contractors/${id}`, data);
return response.data;
},
delete: async (id: number): Promise<void> => {
await api.delete(`/contractors/${id}`);
},
bulkDelete: async (ids: number[]): Promise<void> => {
await api.delete('/contractors/bulk', { data: { ids } });
},
};
// Documents API
export const documentsApi = {
list: async (params?: DocumentListParams): Promise<PaginatedResponse<Document>> => {
const response = await api.get<PaginatedResponse<Document>>('/documents', { params });
return response.data;
},
get: async (id: number): Promise<DocumentDetail> => {
const response = await api.get<DocumentDetail>(`/documents/${id}`);
return response.data;
},
create: async (data: CreateDocumentRequest): Promise<Document> => {
const response = await api.post<Document>('/documents', data);
return response.data;
},
update: async (id: number, data: UpdateDocumentRequest): Promise<Document> => {
const response = await api.put<Document>(`/documents/${id}`, data);
return response.data;
},
delete: async (id: number): Promise<void> => {
await api.delete(`/documents/${id}`);
},
bulkDelete: async (ids: number[]): Promise<void> => {
await api.delete('/documents/bulk', { data: { ids } });
},
};
// Notifications API
import type { UpdateNotificationRequest, UpdateCompletionRequest } from '@/types/models';
export const notificationsApi = {
list: async (params?: NotificationListParams): Promise<PaginatedResponse<Notification>> => {
const response = await api.get<PaginatedResponse<Notification>>('/notifications', { params });
return response.data;
},
get: async (id: number): Promise<NotificationDetail> => {
const response = await api.get<NotificationDetail>(`/notifications/${id}`);
return response.data;
},
update: async (id: number, data: UpdateNotificationRequest): Promise<Notification> => {
const response = await api.put<Notification>(`/notifications/${id}`, data);
return response.data;
},
delete: async (id: number): Promise<void> => {
await api.delete(`/notifications/${id}`);
},
getStats: async (): Promise<NotificationStats> => {
const response = await api.get<NotificationStats>('/notifications/stats');
return response.data;
},
sendTestNotification: async (data: { user_id: number; title: string; body: string }): Promise<{ message: string; notification_id: number; devices: { ios: number; android: number } }> => {
const response = await api.post('/notifications/send-test', data);
return response.data;
},
};
// Emails API
export const emailsApi = {
sendTestEmail: async (data: { user_id: number; subject: string; body: string }): Promise<{ message: string; to: string }> => {
const response = await api.post('/emails/send-test', data);
return response.data;
},
};
// Subscriptions API
export const subscriptionsApi = {
list: async (params?: SubscriptionListParams): Promise<PaginatedResponse<Subscription>> => {
const response = await api.get<PaginatedResponse<Subscription>>('/subscriptions', { params });
return response.data;
},
get: async (id: number): Promise<SubscriptionDetail> => {
const response = await api.get<SubscriptionDetail>(`/subscriptions/${id}`);
return response.data;
},
getByUser: async (userId: number): Promise<Subscription> => {
const response = await api.get<Subscription>(`/subscriptions/user/${userId}`);
return response.data;
},
update: async (id: number, data: UpdateSubscriptionRequest): Promise<Subscription> => {
const response = await api.put<Subscription>(`/subscriptions/${id}`, data);
return response.data;
},
getStats: async (): Promise<SubscriptionStats> => {
const response = await api.get<SubscriptionStats>('/subscriptions/stats');
return response.data;
},
};
// Dashboard API
export const dashboardApi = {
getStats: async (): Promise<DashboardStats> => {
const response = await api.get<DashboardStats>('/dashboard/stats');
return response.data;
},
};
// Auth Tokens Types
export interface AuthToken {
key: string;
user_id: number;
username: string;
email: string;
created: string;
}
export interface AuthTokenListParams {
page?: number;
per_page?: number;
search?: string;
sort_by?: string;
sort_dir?: 'asc' | 'desc';
}
// Auth Tokens API
export const authTokensApi = {
list: async (params?: AuthTokenListParams): Promise<PaginatedResponse<AuthToken>> => {
const response = await api.get<PaginatedResponse<AuthToken>>('/auth-tokens', { params });
return response.data;
},
get: async (userId: number): Promise<AuthToken> => {
const response = await api.get<AuthToken>(`/auth-tokens/${userId}`);
return response.data;
},
revoke: async (userId: number): Promise<void> => {
await api.delete(`/auth-tokens/${userId}`);
},
bulkRevoke: async (userIds: number[]): Promise<void> => {
await api.delete('/auth-tokens/bulk', { data: { ids: userIds } });
},
};
// Task Completion Image
export interface TaskCompletionImage {
id: number;
image_url: string;
caption: string;
}
// Task Completions Types
export interface TaskCompletion {
id: number;
task_id: number;
task_title: string;
residence_id: number;
residence_name: string;
completed_by_id: number;
completed_by: string;
completed_at: string;
notes: string;
actual_cost: string | null;
rating: number | null;
images: TaskCompletionImage[];
created_at: string;
}
export interface CompletionListParams {
page?: number;
per_page?: number;
search?: string;
sort_by?: string;
sort_dir?: 'asc' | 'desc';
task_id?: number;
residence_id?: number;
user_id?: number;
}
// Completions API
export const completionsApi = {
list: async (params?: CompletionListParams): Promise<PaginatedResponse<TaskCompletion>> => {
const response = await api.get<PaginatedResponse<TaskCompletion>>('/completions', { params });
return response.data;
},
get: async (id: number): Promise<TaskCompletion> => {
const response = await api.get<TaskCompletion>(`/completions/${id}`);
return response.data;
},
update: async (id: number, data: UpdateCompletionRequest): Promise<TaskCompletion> => {
const response = await api.put<TaskCompletion>(`/completions/${id}`, data);
return response.data;
},
delete: async (id: number): Promise<void> => {
await api.delete(`/completions/${id}`);
},
bulkDelete: async (ids: number[]): Promise<void> => {
await api.delete('/completions/bulk', { data: { ids } });
},
};
// Lookup Types
export interface LookupItem {
id: number;
name: string;
display_order: number;
description?: string;
icon?: string;
color?: string;
}
export interface CreateLookupRequest {
name: string;
display_order?: number;
description?: string;
icon?: string;
color?: string;
}
export interface UpdateLookupRequest {
name?: string;
display_order?: number;
description?: string;
icon?: string;
color?: string;
}
interface LookupListResponse {
data: LookupItem[];
total: number;
}
// Lookups API Factory
const createLookupApi = (endpoint: string) => ({
list: async (): Promise<LookupItem[]> => {
const response = await api.get<LookupListResponse>(`/lookups/${endpoint}`);
return response.data.data;
},
create: async (data: CreateLookupRequest): Promise<LookupItem> => {
const response = await api.post<LookupItem>(`/lookups/${endpoint}`, data);
return response.data;
},
update: async (id: number, data: UpdateLookupRequest): Promise<LookupItem> => {
const response = await api.put<LookupItem>(`/lookups/${endpoint}/${id}`, data);
return response.data;
},
delete: async (id: number): Promise<void> => {
await api.delete(`/lookups/${endpoint}/${id}`);
},
});
// Lookups API
export const lookupsApi = {
categories: createLookupApi('categories'),
priorities: createLookupApi('priorities'),
statuses: createLookupApi('statuses'),
frequencies: createLookupApi('frequencies'),
residenceTypes: createLookupApi('residence-types'),
specialties: createLookupApi('specialties'),
};
// Admin Users Types
export interface ManagedAdminUser {
id: number;
email: string;
first_name: string;
last_name: string;
role: 'super_admin' | 'admin';
is_active: boolean;
last_login?: string;
created_at: string;
}
export interface AdminUserListParams {
page?: number;
per_page?: number;
search?: string;
sort_by?: string;
sort_dir?: 'asc' | 'desc';
role?: string;
is_active?: boolean;
}
export interface CreateAdminUserRequest {
email: string;
password: string;
first_name?: string;
last_name?: string;
role?: 'super_admin' | 'admin';
is_active?: boolean;
}
export interface UpdateAdminUserRequest {
email?: string;
password?: string;
first_name?: string;
last_name?: string;
role?: 'super_admin' | 'admin';
is_active?: boolean;
}
// Admin Users API
export const adminUsersApi = {
list: async (params?: AdminUserListParams): Promise<PaginatedResponse<ManagedAdminUser>> => {
const response = await api.get<PaginatedResponse<ManagedAdminUser>>('/admin-users', { params });
return response.data;
},
get: async (id: number): Promise<ManagedAdminUser> => {
const response = await api.get<ManagedAdminUser>(`/admin-users/${id}`);
return response.data;
},
create: async (data: CreateAdminUserRequest): Promise<ManagedAdminUser> => {
const response = await api.post<ManagedAdminUser>('/admin-users', data);
return response.data;
},
update: async (id: number, data: UpdateAdminUserRequest): Promise<ManagedAdminUser> => {
const response = await api.put<ManagedAdminUser>(`/admin-users/${id}`, data);
return response.data;
},
delete: async (id: number): Promise<void> => {
await api.delete(`/admin-users/${id}`);
},
};
// Notification Preferences Types
export interface NotificationPreference {
id: number;
user_id: number;
username: string;
email: string;
task_due_soon: boolean;
task_overdue: boolean;
task_completed: boolean;
task_assigned: boolean;
residence_shared: boolean;
warranty_expiring: boolean;
// Email preferences
email_task_completed: boolean;
created_at: string;
updated_at: string;
}
export interface NotificationPrefsListParams {
page?: number;
per_page?: number;
search?: string;
sort_by?: string;
sort_dir?: 'asc' | 'desc';
}
export interface UpdateNotificationPrefRequest {
task_due_soon?: boolean;
task_overdue?: boolean;
task_completed?: boolean;
task_assigned?: boolean;
residence_shared?: boolean;
warranty_expiring?: boolean;
// Email preferences
email_task_completed?: boolean;
}
// Notification Preferences API
export const notificationPrefsApi = {
list: async (params?: NotificationPrefsListParams): Promise<PaginatedResponse<NotificationPreference>> => {
const response = await api.get<PaginatedResponse<NotificationPreference>>('/notification-prefs', { params });
return response.data;
},
get: async (id: number): Promise<NotificationPreference> => {
const response = await api.get<NotificationPreference>(`/notification-prefs/${id}`);
return response.data;
},
getByUser: async (userId: number): Promise<NotificationPreference> => {
const response = await api.get<NotificationPreference>(`/notification-prefs/user/${userId}`);
return response.data;
},
update: async (id: number, data: UpdateNotificationPrefRequest): Promise<NotificationPreference> => {
const response = await api.put<NotificationPreference>(`/notification-prefs/${id}`, data);
return response.data;
},
delete: async (id: number): Promise<void> => {
await api.delete(`/notification-prefs/${id}`);
},
};
// Settings types
export interface SystemSettings {
enable_limitations: boolean;
}
export interface UpdateSettingsRequest {
enable_limitations?: boolean;
}
// Settings API
export const settingsApi = {
get: async (): Promise<SystemSettings> => {
const response = await api.get<SystemSettings>('/settings');
return response.data;
},
update: async (data: UpdateSettingsRequest): Promise<SystemSettings> => {
const response = await api.put<SystemSettings>('/settings', data);
return response.data;
},
seedLookups: async (): Promise<{ message: string }> => {
const response = await api.post<{ message: string }>('/settings/seed-lookups');
return response.data;
},
seedTestData: async (): Promise<{ message: string }> => {
const response = await api.post<{ message: string }>('/settings/seed-test-data');
return response.data;
},
clearAllData: async (): Promise<{ message: string; users_deleted: number; preserved_users: number }> => {
const response = await api.post<{ message: string; users_deleted: number; preserved_users: number }>('/settings/clear-all-data');
return response.data;
},
clearStuckJobs: async (): Promise<{ message: string; keys_deleted: number; deleted_keys: string[] }> => {
const response = await api.post<{ message: string; keys_deleted: number; deleted_keys: string[] }>('/settings/clear-stuck-jobs');
return response.data;
},
};
// Limitations types
export interface LimitationsSettings {
enable_limitations: boolean;
}
export interface TierLimits {
id: number;
tier: 'free' | 'pro';
properties_limit: number | null;
tasks_limit: number | null;
contractors_limit: number | null;
documents_limit: number | null;
created_at: string;
updated_at: string;
}
export interface UpdateTierLimitsRequest {
properties_limit: number | null;
tasks_limit: number | null;
contractors_limit: number | null;
documents_limit: number | null;
}
export interface UpgradeTrigger {
id: number;
trigger_key: string;
title: string;
message: string;
promo_html: string;
button_text: string;
is_active: boolean;
created_at: string;
updated_at: string;
}
export interface TriggerKeyOption {
key: string;
label: string;
}
export interface CreateUpgradeTriggerRequest {
trigger_key: string;
title: string;
message: string;
promo_html?: string;
button_text?: string;
is_active?: boolean;
}
export interface UpdateUpgradeTriggerRequest {
trigger_key?: string;
title?: string;
message?: string;
promo_html?: string;
button_text?: string;
is_active?: boolean;
}
// Limitations API
export const limitationsApi = {
// Settings
getSettings: async (): Promise<LimitationsSettings> => {
const response = await api.get<LimitationsSettings>('/limitations/settings');
return response.data;
},
updateSettings: async (data: { enable_limitations: boolean }): Promise<LimitationsSettings> => {
const response = await api.put<LimitationsSettings>('/limitations/settings', data);
return response.data;
},
// Tier Limits
listTierLimits: async (): Promise<TierLimits[]> => {
const response = await api.get<{ data: TierLimits[]; total: number }>('/limitations/tier-limits');
return response.data.data;
},
getTierLimits: async (tier: 'free' | 'pro'): Promise<TierLimits> => {
const response = await api.get<TierLimits>(`/limitations/tier-limits/${tier}`);
return response.data;
},
updateTierLimits: async (tier: 'free' | 'pro', data: UpdateTierLimitsRequest): Promise<TierLimits> => {
const response = await api.put<TierLimits>(`/limitations/tier-limits/${tier}`, data);
return response.data;
},
// Upgrade Triggers
getAvailableTriggerKeys: async (): Promise<TriggerKeyOption[]> => {
const response = await api.get<TriggerKeyOption[]>('/limitations/upgrade-triggers/keys');
return response.data;
},
listUpgradeTriggers: async (): Promise<UpgradeTrigger[]> => {
const response = await api.get<{ data: UpgradeTrigger[]; total: number }>('/limitations/upgrade-triggers');
return response.data.data;
},
getUpgradeTrigger: async (id: number): Promise<UpgradeTrigger> => {
const response = await api.get<UpgradeTrigger>(`/limitations/upgrade-triggers/${id}`);
return response.data;
},
createUpgradeTrigger: async (data: CreateUpgradeTriggerRequest): Promise<UpgradeTrigger> => {
const response = await api.post<UpgradeTrigger>('/limitations/upgrade-triggers', data);
return response.data;
},
updateUpgradeTrigger: async (id: number, data: UpdateUpgradeTriggerRequest): Promise<UpgradeTrigger> => {
const response = await api.put<UpgradeTrigger>(`/limitations/upgrade-triggers/${id}`, data);
return response.data;
},
deleteUpgradeTrigger: async (id: number): Promise<void> => {
await api.delete(`/limitations/upgrade-triggers/${id}`);
},
};
// Confirmation Codes Types
export interface ConfirmationCode {
id: number;
user_id: number;
username: string;
email: string;
code: string;
expires_at: string;
is_used: boolean;
created_at: string;
}
export interface ConfirmationCodeListParams {
page?: number;
per_page?: number;
search?: string;
sort_by?: string;
sort_dir?: 'asc' | 'desc';
}
// Confirmation Codes API
export const confirmationCodesApi = {
list: async (params?: ConfirmationCodeListParams): Promise<PaginatedResponse<ConfirmationCode>> => {
const response = await api.get<PaginatedResponse<ConfirmationCode>>('/confirmation-codes', { params });
return response.data;
},
get: async (id: number): Promise<ConfirmationCode> => {
const response = await api.get<ConfirmationCode>(`/confirmation-codes/${id}`);
return response.data;
},
delete: async (id: number): Promise<void> => {
await api.delete(`/confirmation-codes/${id}`);
},
bulkDelete: async (ids: number[]): Promise<void> => {
await api.delete('/confirmation-codes/bulk', { data: { ids } });
},
};
// Share Codes Types
export interface ShareCode {
id: number;
residence_id: number;
residence_name: string;
code: string;
created_by_id: number;
created_by: string;
is_active: boolean;
expires_at: string | null;
created_at: string;
}
export interface ShareCodeListParams {
page?: number;
per_page?: number;
search?: string;
sort_by?: string;
sort_dir?: 'asc' | 'desc';
}
export interface UpdateShareCodeRequest {
is_active: boolean;
}
// Share Codes API
export const shareCodesApi = {
list: async (params?: ShareCodeListParams): Promise<PaginatedResponse<ShareCode>> => {
const response = await api.get<PaginatedResponse<ShareCode>>('/share-codes', { params });
return response.data;
},
get: async (id: number): Promise<ShareCode> => {
const response = await api.get<ShareCode>(`/share-codes/${id}`);
return response.data;
},
update: async (id: number, data: UpdateShareCodeRequest): Promise<ShareCode> => {
const response = await api.put<ShareCode>(`/share-codes/${id}`, data);
return response.data;
},
delete: async (id: number): Promise<void> => {
await api.delete(`/share-codes/${id}`);
},
bulkDelete: async (ids: number[]): Promise<void> => {
await api.delete('/share-codes/bulk', { data: { ids } });
},
};
// Password Reset Codes Types
export interface PasswordResetCode {
id: number;
user_id: number;
username: string;
email: string;
reset_token: string;
expires_at: string;
used: boolean;
attempts: number;
max_attempts: number;
created_at: string;
}
export interface PasswordResetCodeListParams {
page?: number;
per_page?: number;
search?: string;
sort_by?: string;
sort_dir?: 'asc' | 'desc';
}
// Password Reset Codes API
export const passwordResetCodesApi = {
list: async (params?: PasswordResetCodeListParams): Promise<PaginatedResponse<PasswordResetCode>> => {
const response = await api.get<PaginatedResponse<PasswordResetCode>>('/password-reset-codes', { params });
return response.data;
},
get: async (id: number): Promise<PasswordResetCode> => {
const response = await api.get<PasswordResetCode>(`/password-reset-codes/${id}`);
return response.data;
},
delete: async (id: number): Promise<void> => {
await api.delete(`/password-reset-codes/${id}`);
},
bulkDelete: async (ids: number[]): Promise<void> => {
await api.delete('/password-reset-codes/bulk', { data: { ids } });
},
};
// Device Types
export interface APNSDevice {
id: number;
name: string;
active: boolean;
user_id: number | null;
username: string | null;
device_id: string;
registration_id: string;
date_created: string;
}
export interface GCMDevice {
id: number;
name: string;
active: boolean;
user_id: number | null;
username: string | null;
device_id: string;
registration_id: string;
cloud_message_type: string;
date_created: string;
}
export interface DeviceListParams {
page?: number;
per_page?: number;
search?: string;
sort_by?: string;
sort_dir?: 'asc' | 'desc';
}
export interface DeviceStats {
apns: { total: number; active: number };
gcm: { total: number; active: number };
total: number;
}
// Devices API
export const devicesApi = {
getStats: async (): Promise<DeviceStats> => {
const response = await api.get<DeviceStats>('/devices/stats');
return response.data;
},
listAPNS: async (params?: DeviceListParams): Promise<PaginatedResponse<APNSDevice>> => {
const response = await api.get<PaginatedResponse<APNSDevice>>('/devices/apns', { params });
return response.data;
},
listGCM: async (params?: DeviceListParams): Promise<PaginatedResponse<GCMDevice>> => {
const response = await api.get<PaginatedResponse<GCMDevice>>('/devices/gcm', { params });
return response.data;
},
updateAPNS: async (id: number, data: { active: boolean }): Promise<void> => {
await api.put(`/devices/apns/${id}`, data);
},
updateGCM: async (id: number, data: { active: boolean }): Promise<void> => {
await api.put(`/devices/gcm/${id}`, data);
},
deleteAPNS: async (id: number): Promise<void> => {
await api.delete(`/devices/apns/${id}`);
},
deleteGCM: async (id: number): Promise<void> => {
await api.delete(`/devices/gcm/${id}`);
},
bulkDeleteAPNS: async (ids: number[]): Promise<void> => {
await api.delete('/devices/apns/bulk', { data: { ids } });
},
bulkDeleteGCM: async (ids: number[]): Promise<void> => {
await api.delete('/devices/gcm/bulk', { data: { ids } });
},
};
// Feature Benefit Types
export interface FeatureBenefit {
id: number;
feature_name: string;
free_tier_text: string;
pro_tier_text: string;
display_order: number;
is_active: boolean;
created_at: string;
updated_at: string;
}
export interface CreateFeatureBenefitRequest {
feature_name: string;
free_tier_text: string;
pro_tier_text: string;
display_order?: number;
is_active?: boolean;
}
export interface UpdateFeatureBenefitRequest {
feature_name?: string;
free_tier_text?: string;
pro_tier_text?: string;
display_order?: number;
is_active?: boolean;
}
// Feature Benefits API
export const featureBenefitsApi = {
list: async (params?: { page?: number; per_page?: number; search?: string }): Promise<PaginatedResponse<FeatureBenefit>> => {
const response = await api.get<PaginatedResponse<FeatureBenefit>>('/feature-benefits', { params });
return response.data;
},
get: async (id: number): Promise<FeatureBenefit> => {
const response = await api.get<FeatureBenefit>(`/feature-benefits/${id}`);
return response.data;
},
create: async (data: CreateFeatureBenefitRequest): Promise<FeatureBenefit> => {
const response = await api.post<FeatureBenefit>('/feature-benefits', data);
return response.data;
},
update: async (id: number, data: UpdateFeatureBenefitRequest): Promise<FeatureBenefit> => {
const response = await api.put<FeatureBenefit>(`/feature-benefits/${id}`, data);
return response.data;
},
delete: async (id: number): Promise<void> => {
await api.delete(`/feature-benefits/${id}`);
},
};
// Promotion Types
export interface Promotion {
id: number;
promotion_id: string;
title: string;
message: string;
link: string | null;
start_date: string;
end_date: string;
target_tier: 'free' | 'pro';
is_active: boolean;
created_at: string;
updated_at: string;
}
export interface CreatePromotionRequest {
promotion_id: string;
title: string;
message: string;
link?: string;
start_date: string;
end_date: string;
target_tier?: 'free' | 'pro';
is_active?: boolean;
}
export interface UpdatePromotionRequest {
promotion_id?: string;
title?: string;
message?: string;
link?: string;
start_date?: string;
end_date?: string;
target_tier?: 'free' | 'pro';
is_active?: boolean;
}
// Promotions API
export const promotionsApi = {
list: async (params?: { page?: number; per_page?: number; search?: string }): Promise<PaginatedResponse<Promotion>> => {
const response = await api.get<PaginatedResponse<Promotion>>('/promotions', { params });
return response.data;
},
get: async (id: number): Promise<Promotion> => {
const response = await api.get<Promotion>(`/promotions/${id}`);
return response.data;
},
create: async (data: CreatePromotionRequest): Promise<Promotion> => {
const response = await api.post<Promotion>('/promotions', data);
return response.data;
},
update: async (id: number, data: UpdatePromotionRequest): Promise<Promotion> => {
const response = await api.put<Promotion>(`/promotions/${id}`, data);
return response.data;
},
delete: async (id: number): Promise<void> => {
await api.delete(`/promotions/${id}`);
},
};
// Task Template Types
export interface TaskTemplateCategory {
id: number;
name: string;
description: string;
icon: string;
color: string;
display_order: number;
}
export interface TaskTemplateFrequency {
id: number;
name: string;
days: number;
display_order: number;
}
export interface TaskTemplate {
id: number;
title: string;
description: string;
category_id: number | null;
category?: TaskTemplateCategory;
frequency_id: number | null;
frequency?: TaskTemplateFrequency;
icon_ios: string;
icon_android: string;
tags: string;
display_order: number;
is_active: boolean;
created_at: string;
updated_at: string;
}
export interface TaskTemplateListParams {
page?: number;
per_page?: number;
search?: string;
sort_by?: string;
sort_dir?: 'asc' | 'desc';
category_id?: number;
frequency_id?: number;
is_active?: boolean;
}
export interface CreateTaskTemplateRequest {
title: string;
description?: string;
category_id?: number;
frequency_id?: number;
icon_ios?: string;
icon_android?: string;
tags?: string;
display_order?: number;
is_active?: boolean;
}
export interface UpdateTaskTemplateRequest {
title?: string;
description?: string;
category_id?: number | null;
frequency_id?: number | null;
icon_ios?: string;
icon_android?: string;
tags?: string;
display_order?: number;
is_active?: boolean;
}
// Task Templates API
export const taskTemplatesApi = {
list: async (params?: TaskTemplateListParams): Promise<PaginatedResponse<TaskTemplate>> => {
const response = await api.get<PaginatedResponse<TaskTemplate>>('/task-templates', { params });
return response.data;
},
get: async (id: number): Promise<TaskTemplate> => {
const response = await api.get<TaskTemplate>(`/task-templates/${id}`);
return response.data;
},
create: async (data: CreateTaskTemplateRequest): Promise<TaskTemplate> => {
const response = await api.post<TaskTemplate>('/task-templates', data);
return response.data;
},
update: async (id: number, data: UpdateTaskTemplateRequest): Promise<TaskTemplate> => {
const response = await api.put<TaskTemplate>(`/task-templates/${id}`, data);
return response.data;
},
delete: async (id: number): Promise<void> => {
await api.delete(`/task-templates/${id}`);
},
toggleActive: async (id: number): Promise<TaskTemplate> => {
const response = await api.post<TaskTemplate>(`/task-templates/${id}/toggle-active`);
return response.data;
},
};
export default api;