Replace all mock data with real backend integration
- Add database tables for email configs, settings, and activity logs - Create backend endpoints for dashboard stats, analytics, archives, email config, and settings - Create frontend service layer (admin, archive, email, settings services) - Update AdminDashboard to use real statistics and activity data - Update AnalyticsPage to fetch real analytics from backend - Update ArchivesPage with pagination and real archive operations - Update EmailConfigPage to manage real SMTP config and templates - Remove all mock data and replace with API calls throughout admin interface 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
import { api } from '../config/api';
|
||||
|
||||
export interface DashboardStats {
|
||||
activeEvents: number;
|
||||
expiringEvents: number;
|
||||
totalPhotos: number;
|
||||
storageUsed: number;
|
||||
totalViews: number;
|
||||
totalDownloads: number;
|
||||
viewsTrend: number;
|
||||
downloadsTrend: number;
|
||||
}
|
||||
|
||||
export interface Activity {
|
||||
id: number;
|
||||
type: string;
|
||||
actorType: string;
|
||||
actorName: string;
|
||||
eventName?: string;
|
||||
metadata: Record<string, any>;
|
||||
createdAt: string;
|
||||
}
|
||||
|
||||
export interface AnalyticsData {
|
||||
chartData: Array<{
|
||||
date: string;
|
||||
views: number;
|
||||
downloads: number;
|
||||
uniqueVisitors: number;
|
||||
}>;
|
||||
topGalleries: Array<{
|
||||
event_name: string;
|
||||
slug: string;
|
||||
views: number;
|
||||
}>;
|
||||
devices: {
|
||||
desktop: number;
|
||||
mobile: number;
|
||||
tablet: number;
|
||||
};
|
||||
}
|
||||
|
||||
export const adminService = {
|
||||
// Dashboard statistics
|
||||
async getDashboardStats(): Promise<DashboardStats> {
|
||||
const response = await api.get<DashboardStats>('/api/admin/dashboard/stats');
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// Recent activity
|
||||
async getRecentActivity(limit: number = 10): Promise<Activity[]> {
|
||||
const response = await api.get<Activity[]>('/api/admin/dashboard/activity', {
|
||||
params: { limit }
|
||||
});
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// Analytics data
|
||||
async getAnalytics(days: number = 7): Promise<AnalyticsData> {
|
||||
const response = await api.get<AnalyticsData>('/api/admin/dashboard/analytics', {
|
||||
params: { days }
|
||||
});
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// Format activity message
|
||||
formatActivityMessage(activity: Activity): string {
|
||||
const messages: Record<string, string> = {
|
||||
'event_created': `New event created: ${activity.eventName || 'Unknown'}`,
|
||||
'photos_uploaded': `${activity.metadata.count || 0} photos uploaded to ${activity.eventName || 'Unknown'}`,
|
||||
'event_archived': `Event archived: ${activity.eventName || 'Unknown'}`,
|
||||
'archive_restored': `Archive restored: ${activity.eventName || 'Unknown'}`,
|
||||
'archive_deleted': `Archive deleted: ${activity.metadata.event_name || 'Unknown'}`,
|
||||
'archive_downloaded': `Archive downloaded: ${activity.eventName || 'Unknown'}`,
|
||||
'email_config_updated': 'Email configuration updated',
|
||||
'email_template_updated': `Email template updated: ${activity.metadata.template_key || ''}`,
|
||||
'branding_updated': 'Branding settings updated',
|
||||
'theme_updated': 'Theme settings updated',
|
||||
'bulk_download': `${activity.metadata.photo_count || 0} photos downloaded from ${activity.eventName || 'Unknown'}`,
|
||||
'gallery_password_entry': `Password entered for ${activity.eventName || 'Unknown'}`,
|
||||
'expiration_warning_viewed': `Expiration warning viewed for ${activity.eventName || 'Unknown'}`
|
||||
};
|
||||
|
||||
return messages[activity.type] || activity.type;
|
||||
},
|
||||
|
||||
// Format bytes to human readable
|
||||
formatBytes(bytes: number): string {
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
const k = 1024;
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,96 @@
|
||||
import { api } from '../config/api';
|
||||
|
||||
export interface Archive {
|
||||
id: number;
|
||||
slug: string;
|
||||
eventName: string;
|
||||
eventDate: string;
|
||||
eventType: string;
|
||||
hostEmail: string;
|
||||
archivedAt: string;
|
||||
expiresAt: string;
|
||||
photoCount: number;
|
||||
originalSize: number;
|
||||
archiveSize: number;
|
||||
archivePath?: string;
|
||||
}
|
||||
|
||||
export interface ArchiveDetails extends Archive {
|
||||
adminEmail: string;
|
||||
welcomeMessage?: string;
|
||||
colorTheme?: string;
|
||||
createdAt: string;
|
||||
photos: Array<{
|
||||
filename: string;
|
||||
type: string;
|
||||
size_bytes: number;
|
||||
uploaded_at: string;
|
||||
}>;
|
||||
archiveFile?: {
|
||||
size: number;
|
||||
createdAt: string;
|
||||
path: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface ArchivesResponse {
|
||||
archives: Archive[];
|
||||
pagination: {
|
||||
page: number;
|
||||
limit: number;
|
||||
total: number;
|
||||
totalPages: number;
|
||||
};
|
||||
}
|
||||
|
||||
export const archiveService = {
|
||||
// Get all archives with pagination
|
||||
async getArchives(page: number = 1, limit: number = 20): Promise<ArchivesResponse> {
|
||||
const response = await api.get<ArchivesResponse>('/api/admin/archives', {
|
||||
params: { page, limit }
|
||||
});
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// Get single archive details
|
||||
async getArchiveDetails(id: number): Promise<ArchiveDetails> {
|
||||
const response = await api.get<ArchiveDetails>(`/api/admin/archives/${id}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// Restore archive
|
||||
async restoreArchive(id: number): Promise<void> {
|
||||
await api.post(`/api/admin/archives/${id}/restore`);
|
||||
},
|
||||
|
||||
// Download archive
|
||||
async downloadArchive(id: number, filename: string): Promise<void> {
|
||||
const response = await api.get(`/api/admin/archives/${id}/download`, {
|
||||
responseType: 'blob'
|
||||
});
|
||||
|
||||
// Create download link
|
||||
const url = window.URL.createObjectURL(new Blob([response.data]));
|
||||
const link = document.createElement('a');
|
||||
link.href = url;
|
||||
link.setAttribute('download', filename);
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
link.remove();
|
||||
window.URL.revokeObjectURL(url);
|
||||
},
|
||||
|
||||
// Delete archive permanently
|
||||
async deleteArchive(id: number): Promise<void> {
|
||||
await api.delete(`/api/admin/archives/${id}`);
|
||||
},
|
||||
|
||||
// Format bytes to human readable
|
||||
formatBytes(bytes: number): string {
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
const k = 1024;
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,71 @@
|
||||
import { api } from '../config/api';
|
||||
|
||||
export interface EmailConfig {
|
||||
smtp_host: string;
|
||||
smtp_port: number;
|
||||
smtp_secure: boolean;
|
||||
smtp_user: string;
|
||||
smtp_pass: string;
|
||||
from_email: string;
|
||||
from_name: string;
|
||||
}
|
||||
|
||||
export interface EmailTemplate {
|
||||
id: number;
|
||||
template_key: string;
|
||||
subject: string;
|
||||
body_html: string;
|
||||
body_text?: string;
|
||||
variables: string[];
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface EmailPreview {
|
||||
subject: string;
|
||||
body_html: string;
|
||||
body_text: string;
|
||||
}
|
||||
|
||||
export const emailService = {
|
||||
// Get email configuration
|
||||
async getConfig(): Promise<EmailConfig> {
|
||||
const response = await api.get<EmailConfig>('/api/admin/email/config');
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// Update email configuration
|
||||
async updateConfig(config: EmailConfig): Promise<void> {
|
||||
await api.post('/api/admin/email/config', config);
|
||||
},
|
||||
|
||||
// Test email configuration
|
||||
async testEmail(testEmail: string): Promise<void> {
|
||||
await api.post('/api/admin/email/test', { test_email: testEmail });
|
||||
},
|
||||
|
||||
// Get all email templates
|
||||
async getTemplates(): Promise<EmailTemplate[]> {
|
||||
const response = await api.get<EmailTemplate[]>('/api/admin/email/templates');
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// Get single template
|
||||
async getTemplate(key: string): Promise<EmailTemplate> {
|
||||
const response = await api.get<EmailTemplate>(`/api/admin/email/templates/${key}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// Update email template
|
||||
async updateTemplate(key: string, template: Partial<EmailTemplate>): Promise<void> {
|
||||
await api.put(`/api/admin/email/templates/${key}`, template);
|
||||
},
|
||||
|
||||
// Preview email template
|
||||
async previewTemplate(key: string, previewData: Record<string, string>): Promise<EmailPreview> {
|
||||
const response = await api.post<EmailPreview>(
|
||||
`/api/admin/email/templates/${key}/preview`,
|
||||
{ preview_data: previewData }
|
||||
);
|
||||
return response.data;
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,97 @@
|
||||
import { api } from '../config/api';
|
||||
|
||||
export interface BrandingSettings {
|
||||
company_name: string;
|
||||
company_tagline: string;
|
||||
support_email: string;
|
||||
footer_text: string;
|
||||
watermark_enabled: boolean;
|
||||
logo_url?: string;
|
||||
}
|
||||
|
||||
export interface ThemeSettings {
|
||||
name?: string;
|
||||
primaryColor?: string;
|
||||
accentColor?: string;
|
||||
backgroundColor?: string;
|
||||
textColor?: string;
|
||||
fontFamily?: string;
|
||||
borderRadius?: 'none' | 'sm' | 'md' | 'lg';
|
||||
customCss?: string;
|
||||
}
|
||||
|
||||
export interface StorageInfo {
|
||||
total_used: number;
|
||||
archive_storage: number;
|
||||
storage_by_event: Array<{
|
||||
event_name: string;
|
||||
id: number;
|
||||
size: number;
|
||||
}>;
|
||||
storage_limit: number;
|
||||
}
|
||||
|
||||
export const settingsService = {
|
||||
// Get all settings
|
||||
async getAllSettings(): Promise<Record<string, any>> {
|
||||
const response = await api.get<Record<string, any>>('/api/admin/settings');
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// Get settings by type
|
||||
async getSettingsByType(type: 'branding' | 'theme' | 'general'): Promise<Record<string, any>> {
|
||||
const response = await api.get<Record<string, any>>(`/api/admin/settings/${type}`);
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// Update branding settings
|
||||
async updateBranding(settings: BrandingSettings): Promise<void> {
|
||||
await api.put('/api/admin/settings/branding', settings);
|
||||
},
|
||||
|
||||
// Upload logo
|
||||
async uploadLogo(file: File): Promise<{ logo_url: string }> {
|
||||
const formData = new FormData();
|
||||
formData.append('logo', file);
|
||||
|
||||
const response = await api.post<{ message: string; logo_url: string }>(
|
||||
'/api/admin/settings/logo',
|
||||
formData,
|
||||
{
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data'
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return { logo_url: response.data.logo_url };
|
||||
},
|
||||
|
||||
// Update theme settings
|
||||
async updateTheme(settings: ThemeSettings): Promise<void> {
|
||||
await api.put('/api/admin/settings/theme', settings);
|
||||
},
|
||||
|
||||
// Get storage information
|
||||
async getStorageInfo(): Promise<StorageInfo> {
|
||||
const response = await api.get<StorageInfo>('/api/admin/settings/storage/info');
|
||||
return response.data;
|
||||
},
|
||||
|
||||
// Format branding settings from raw data
|
||||
formatBrandingSettings(rawSettings: Record<string, any>): BrandingSettings {
|
||||
return {
|
||||
company_name: rawSettings.branding_company_name || '',
|
||||
company_tagline: rawSettings.branding_company_tagline || '',
|
||||
support_email: rawSettings.branding_support_email || '',
|
||||
footer_text: rawSettings.branding_footer_text || '',
|
||||
watermark_enabled: rawSettings.branding_watermark_enabled || false,
|
||||
logo_url: rawSettings.branding_logo_url || undefined
|
||||
};
|
||||
},
|
||||
|
||||
// Format theme settings from raw data
|
||||
formatThemeSettings(rawSettings: Record<string, any>): ThemeSettings {
|
||||
return rawSettings.theme_config || {};
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user