front fix
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -24,7 +24,6 @@ import {
|
||||
ArrowLeft
|
||||
} from 'lucide-react';
|
||||
import CommentPreview from './CommentPreview';
|
||||
import { commentsApi, postsApi } from '../utils/api';
|
||||
|
||||
// 定义后端返回的评论类型
|
||||
interface ApiComment {
|
||||
@@ -96,105 +95,82 @@ const CommentList: React.FC<CommentListProps> = () => {
|
||||
const [totalComments, setTotalComments] = useState<number>(0);
|
||||
const [showFilters, setShowFilters] = useState<boolean>(false);
|
||||
|
||||
// Fetch post data if postId is provided
|
||||
// Fetch post details if postId is provided
|
||||
useEffect(() => {
|
||||
const fetchPostData = async () => {
|
||||
if (postId) {
|
||||
try {
|
||||
const response = await postsApi.getPost(postId);
|
||||
setPost(response.data);
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch post data:', err);
|
||||
}
|
||||
const fetchPostDetails = async () => {
|
||||
if (!postId) return;
|
||||
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
// Mock post data
|
||||
const mockPost = {
|
||||
id: postId,
|
||||
title: 'Sample Post Title',
|
||||
content: 'This is a sample post content for demonstration purposes.',
|
||||
platform: 'Facebook',
|
||||
url: 'https://facebook.com/sample-post'
|
||||
};
|
||||
|
||||
setPost(mockPost);
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
console.error('Error fetching post details:', error);
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchPostData();
|
||||
fetchPostDetails();
|
||||
}, [postId]);
|
||||
|
||||
// 获取评论数据
|
||||
// Fetch comments
|
||||
useEffect(() => {
|
||||
const fetchComments = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
// Build query parameters
|
||||
const params: Record<string, string | number> = {};
|
||||
|
||||
if (postId) {
|
||||
params.post_id = postId;
|
||||
}
|
||||
|
||||
if (platformFilter !== 'all') {
|
||||
params.platform = platformFilter;
|
||||
}
|
||||
|
||||
if (statusFilter !== 'all') {
|
||||
params.status = statusFilter;
|
||||
}
|
||||
|
||||
if (sentimentFilter !== 'all') {
|
||||
params.sentiment = sentimentFilter;
|
||||
}
|
||||
|
||||
if (searchQuery) {
|
||||
params.query = searchQuery;
|
||||
}
|
||||
|
||||
if (languageFilter !== 'all') {
|
||||
params.language = languageFilter;
|
||||
}
|
||||
|
||||
// Add pagination
|
||||
params.limit = pageSize;
|
||||
params.offset = (currentPage - 1) * pageSize;
|
||||
|
||||
const response = await commentsApi.getComments(params);
|
||||
|
||||
// 处理返回的数据
|
||||
const apiComments: ApiComment[] = response.data.comments || [];
|
||||
const total = response.data.total || apiComments.length;
|
||||
|
||||
// 转换为前端格式
|
||||
const frontendComments: FrontendComment[] = apiComments.map(comment => {
|
||||
// 确定情感
|
||||
let sentiment = 'neutral';
|
||||
if (comment.sentiment_score > 0.3) {
|
||||
sentiment = 'positive';
|
||||
} else if (comment.sentiment_score < -0.3) {
|
||||
sentiment = 'negative';
|
||||
// Mock comments data
|
||||
const mockComments = [
|
||||
{
|
||||
id: '1',
|
||||
content: 'Great post! I really enjoyed reading this.',
|
||||
author: 'John Smith',
|
||||
timestamp: '2023-05-15T10:30:00Z',
|
||||
platform: 'Facebook',
|
||||
sentiment: 'positive',
|
||||
status: 'approved'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
content: 'This was very helpful, thanks for sharing!',
|
||||
author: 'Sarah Johnson',
|
||||
timestamp: '2023-05-14T14:45:00Z',
|
||||
platform: 'Twitter',
|
||||
sentiment: 'positive',
|
||||
status: 'pending'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
content: 'I have a question about the third point you mentioned...',
|
||||
author: 'Michael Brown',
|
||||
timestamp: '2023-05-13T09:15:00Z',
|
||||
platform: 'Instagram',
|
||||
sentiment: 'neutral',
|
||||
status: 'approved'
|
||||
}
|
||||
];
|
||||
|
||||
// 检测语言
|
||||
const language = detectLanguage(comment.content);
|
||||
|
||||
return {
|
||||
id: comment.comment_id,
|
||||
content: comment.content,
|
||||
author: comment.user_profile?.full_name || '匿名用户',
|
||||
authorType: 'user', // 默认为普通用户
|
||||
platform: 'facebook', // 假设默认是 Facebook
|
||||
timestamp: comment.created_at,
|
||||
sentiment,
|
||||
status: 'approved', // 假设默认已审核
|
||||
language,
|
||||
// 其他可选字段可以根据 API 返回的数据动态添加
|
||||
};
|
||||
});
|
||||
|
||||
setComments(frontendComments);
|
||||
setTotalComments(total);
|
||||
setError(null);
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch comments:', err);
|
||||
setError('加载评论失败,请稍后再试');
|
||||
} finally {
|
||||
setComments(mockComments);
|
||||
setTotalComments(mockComments.length);
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
console.error('Error fetching comments:', error);
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchComments();
|
||||
}, [postId, platformFilter, statusFilter, sentimentFilter, searchQuery, languageFilter, currentPage, pageSize]);
|
||||
}, [postId, currentPage, pageSize, statusFilter, platformFilter, sentimentFilter]);
|
||||
|
||||
// 简单的语言检测
|
||||
const detectLanguage = (text: string): 'zh-TW' | 'zh-CN' | 'en' => {
|
||||
|
||||
@@ -27,7 +27,6 @@ import {
|
||||
Save,
|
||||
Lock
|
||||
} from 'lucide-react';
|
||||
import { templatesApi } from '../utils/api';
|
||||
|
||||
interface ReplyTemplate {
|
||||
id: string;
|
||||
@@ -51,24 +50,44 @@ const CommentPreview: React.FC<CommentPreviewProps> = ({ comment, onClose }) =>
|
||||
const [templates, setTemplates] = useState<ReplyTemplate[]>([]);
|
||||
const [loadingTemplates, setLoadingTemplates] = useState(false);
|
||||
|
||||
// Fetch templates from API
|
||||
// Fetch templates
|
||||
useEffect(() => {
|
||||
const fetchTemplates = async () => {
|
||||
if (showTemplates) {
|
||||
try {
|
||||
setLoadingTemplates(true);
|
||||
const response = await templatesApi.getTemplates();
|
||||
setTemplates(response.data.templates || []);
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch reply templates:', err);
|
||||
} finally {
|
||||
setLoadingTemplates(false);
|
||||
}
|
||||
try {
|
||||
setLoadingTemplates(true);
|
||||
|
||||
// Mock templates data
|
||||
const mockTemplates = [
|
||||
{
|
||||
id: '1',
|
||||
title: 'Thank You Response',
|
||||
content: 'Thank you for your feedback! We appreciate your support.',
|
||||
category: 'Appreciation'
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
title: 'Question Response',
|
||||
content: 'Thank you for your question. Our team will look into this and get back to you soon.',
|
||||
category: 'Support'
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
title: 'Complaint Response',
|
||||
content: 'We apologize for the inconvenience. Please contact our support team at support@example.com for assistance.',
|
||||
category: 'Support'
|
||||
}
|
||||
];
|
||||
|
||||
setTemplates(mockTemplates);
|
||||
setLoadingTemplates(false);
|
||||
} catch (error) {
|
||||
console.error('Error fetching templates:', error);
|
||||
setLoadingTemplates(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchTemplates();
|
||||
}, [showTemplates]);
|
||||
}, []);
|
||||
|
||||
const getSentimentIcon = (sentiment: string) => {
|
||||
switch (sentiment) {
|
||||
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
Youtube,
|
||||
Hash
|
||||
} from 'lucide-react';
|
||||
import { commentsApi } from '../utils/api';
|
||||
|
||||
interface Comment {
|
||||
id: string;
|
||||
@@ -31,19 +30,53 @@ interface Comment {
|
||||
const Dashboard: React.FC = () => {
|
||||
const [comments, setComments] = useState<Comment[]>([]);
|
||||
const [loading, setLoading] = useState<boolean>(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchComments = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const response = await commentsApi.getComments();
|
||||
setComments(response.data.comments || []);
|
||||
setError(null);
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch comments:', err);
|
||||
setError('Failed to load dashboard data. Please try again later.');
|
||||
} finally {
|
||||
|
||||
// Mock data for recent comments
|
||||
const mockComments = [
|
||||
{
|
||||
id: '1',
|
||||
content: 'Great post! I really enjoyed reading this.',
|
||||
author: 'John Smith',
|
||||
timestamp: '2023-05-15T10:30:00Z',
|
||||
platform: 'Facebook',
|
||||
authorType: 'Customer',
|
||||
status: 'Approved',
|
||||
sentiment: 'Positive',
|
||||
post: { title: 'Introduction to React Hooks' }
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
content: 'This was very helpful, thanks for sharing!',
|
||||
author: 'Sarah Johnson',
|
||||
timestamp: '2023-05-14T14:45:00Z',
|
||||
platform: 'Twitter',
|
||||
authorType: 'Influencer',
|
||||
status: 'Pending',
|
||||
sentiment: 'Positive',
|
||||
post: { title: 'Advanced CSS Techniques' }
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
content: 'I have a question about the third point you mentioned...',
|
||||
author: 'Michael Brown',
|
||||
timestamp: '2023-05-13T09:15:00Z',
|
||||
platform: 'Instagram',
|
||||
authorType: 'Customer',
|
||||
status: 'Approved',
|
||||
sentiment: 'Neutral',
|
||||
post: { title: 'JavaScript Performance Tips' }
|
||||
}
|
||||
];
|
||||
|
||||
setComments(mockComments);
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
console.error('Error fetching recent comments:', error);
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
@@ -64,7 +97,7 @@ const Dashboard: React.FC = () => {
|
||||
}, {});
|
||||
|
||||
// Get recent comments
|
||||
const recentComments = [...comments]
|
||||
const sortedRecentComments = [...comments]
|
||||
.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime())
|
||||
.slice(0, 5);
|
||||
|
||||
@@ -125,23 +158,6 @@ const Dashboard: React.FC = () => {
|
||||
);
|
||||
}
|
||||
|
||||
if (error) {
|
||||
return (
|
||||
<div className="p-6 flex-1 overflow-y-auto">
|
||||
<div className="bg-red-50 border-l-4 border-red-500 p-4 mb-4">
|
||||
<div className="flex">
|
||||
<div className="flex-shrink-0">
|
||||
<AlertCircle className="h-5 w-5 text-red-500" />
|
||||
</div>
|
||||
<div className="ml-3">
|
||||
<p className="text-sm text-red-700">{error}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex-1 overflow-auto">
|
||||
<div className="p-6">
|
||||
@@ -308,7 +324,7 @@ const Dashboard: React.FC = () => {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody className="bg-white divide-y divide-gray-200">
|
||||
{recentComments.map((comment, index) => (
|
||||
{sortedRecentComments.map((comment, index) => (
|
||||
<tr key={index} className="hover:bg-gray-50">
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<div className="flex items-center">
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import axios from 'axios';
|
||||
import { User } from '../context/AuthContext';
|
||||
import { useAuth } from '../context/AuthContext';
|
||||
import supabase from '../utils/supabase';
|
||||
|
||||
@@ -26,7 +26,6 @@ import {
|
||||
MessageOutlined
|
||||
} from '@ant-design/icons';
|
||||
import { format } from 'date-fns';
|
||||
import { postsApi } from '../utils/api';
|
||||
|
||||
// API response type definition based on backend structure
|
||||
interface ApiPost {
|
||||
@@ -87,76 +86,58 @@ const PostList: React.FC<PostListProps> = ({ influencerId, projectId }) => {
|
||||
const [platformFilter, setPlatformFilter] = useState<string>('all');
|
||||
const [contentTypeFilter, setContentTypeFilter] = useState<string>('all');
|
||||
const [showFilters, setShowFilters] = useState<boolean>(false);
|
||||
const [totalPosts, setTotalPosts] = useState<number>(0);
|
||||
|
||||
// Fetch posts data
|
||||
useEffect(() => {
|
||||
const fetchPosts = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const fetchPosts = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
|
||||
// Build query parameters
|
||||
const params: Record<string, string | number> = {
|
||||
limit: 50,
|
||||
offset: 0
|
||||
};
|
||||
|
||||
if (influencerId) {
|
||||
params.influencer_id = influencerId;
|
||||
// Mock data for posts
|
||||
const mockPosts = [
|
||||
{
|
||||
id: '1',
|
||||
title: 'Introduction to React Hooks',
|
||||
content: 'React Hooks are a powerful feature that allows you to use state and other React features without writing a class.',
|
||||
author: 'John Smith',
|
||||
date: '2023-05-15',
|
||||
platform: 'Facebook',
|
||||
status: 'Published',
|
||||
engagement: 85,
|
||||
comments: 12
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
title: 'Advanced CSS Techniques',
|
||||
content: 'Learn about the latest CSS techniques including Grid, Flexbox, and CSS Variables.',
|
||||
author: 'Sarah Johnson',
|
||||
date: '2023-05-14',
|
||||
platform: 'Twitter',
|
||||
status: 'Draft',
|
||||
engagement: 72,
|
||||
comments: 8
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
title: 'JavaScript Performance Tips',
|
||||
content: 'Optimize your JavaScript code with these performance tips and best practices.',
|
||||
author: 'Michael Brown',
|
||||
date: '2023-05-13',
|
||||
platform: 'LinkedIn',
|
||||
status: 'Published',
|
||||
engagement: 68,
|
||||
comments: 15
|
||||
}
|
||||
];
|
||||
|
||||
if (projectId) {
|
||||
params.project_id = projectId;
|
||||
}
|
||||
|
||||
const response = await postsApi.getPosts(params);
|
||||
|
||||
// Process returned data
|
||||
const apiPosts: ApiPost[] = response.data.posts || [];
|
||||
|
||||
// Transform API posts to frontend format
|
||||
const processedPosts: FrontendPost[] = apiPosts.map((apiPost) => {
|
||||
// Determine content type based on post data
|
||||
let contentType: FrontendPost['contentType'] = 'post';
|
||||
if (apiPost.platform === 'youtube') {
|
||||
contentType = 'video';
|
||||
} else if (
|
||||
apiPost.platform === 'instagram' &&
|
||||
apiPost.post_url?.includes('/reels/')
|
||||
) {
|
||||
contentType = 'reel';
|
||||
}
|
||||
|
||||
return {
|
||||
id: apiPost.post_id,
|
||||
title: apiPost.title || 'Untitled Post',
|
||||
description: apiPost.description || '',
|
||||
author: apiPost.influencer?.name || 'Unknown',
|
||||
authorType: 'influencer',
|
||||
platform: apiPost.platform as FrontendPost['platform'],
|
||||
contentType,
|
||||
timestamp: apiPost.published_at,
|
||||
engagement: {
|
||||
views: apiPost.views_count,
|
||||
likes: apiPost.likes_count,
|
||||
comments: apiPost.comments_count,
|
||||
shares: apiPost.shares_count
|
||||
},
|
||||
url: apiPost.post_url
|
||||
};
|
||||
});
|
||||
|
||||
setPosts(processedPosts);
|
||||
setError(null);
|
||||
} catch (err) {
|
||||
console.error('Failed to fetch posts:', err);
|
||||
setError('Failed to load posts. Please try again later.');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchPosts();
|
||||
}, [influencerId, projectId]);
|
||||
setTotalPosts(mockPosts.length);
|
||||
setPosts(mockPosts);
|
||||
setLoading(false);
|
||||
} catch (error) {
|
||||
console.error('Error fetching posts:', error);
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Filter posts based on selected filters
|
||||
const filteredPosts = posts.filter((post) => {
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import React, { createContext, useState, useContext, useEffect, ReactNode } from 'react';
|
||||
import { authApi } from '../utils/api';
|
||||
import supabase from '../utils/supabase';
|
||||
|
||||
export interface User {
|
||||
|
||||
@@ -1,163 +0,0 @@
|
||||
import axios, { AxiosInstance, AxiosResponse } from 'axios';
|
||||
import supabase from './supabase';
|
||||
|
||||
// Type definitions
|
||||
interface LoginCredentials {
|
||||
email: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
interface LoginResponse {
|
||||
success: boolean;
|
||||
token: string;
|
||||
user: {
|
||||
id: string;
|
||||
email: string;
|
||||
name?: string;
|
||||
};
|
||||
}
|
||||
|
||||
// Create a reusable Axios instance with default configuration
|
||||
const apiClient: AxiosInstance = axios.create({
|
||||
baseURL: 'http://localhost:4000',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
timeout: 10000, // 10 seconds timeout
|
||||
});
|
||||
|
||||
// Request interceptor for adding auth token
|
||||
apiClient.interceptors.request.use(
|
||||
async (config) => {
|
||||
// 从 Supabase 获取当前会话
|
||||
const { data } = await supabase.auth.getSession();
|
||||
const session = data.session;
|
||||
|
||||
if (session) {
|
||||
config.headers.Authorization = `Bearer ${session.access_token}`;
|
||||
}
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
// Response interceptor for handling common errors
|
||||
apiClient.interceptors.response.use(
|
||||
(response) => {
|
||||
return response;
|
||||
},
|
||||
async (error) => {
|
||||
// Handle errors globally
|
||||
if (error.response) {
|
||||
// Server responded with error status (4xx, 5xx)
|
||||
if (error.response.status === 401) {
|
||||
// Unauthorized - 可能是 token 过期,尝试刷新
|
||||
try {
|
||||
const { data, error: refreshError } = await supabase.auth.refreshSession();
|
||||
|
||||
if (refreshError || !data.session) {
|
||||
// 刷新失败,重定向到登录页面
|
||||
if (window.location.pathname !== '/login') {
|
||||
window.location.href = '/login';
|
||||
}
|
||||
} else {
|
||||
// 刷新成功,重试请求
|
||||
const originalRequest = error.config;
|
||||
originalRequest.headers.Authorization = `Bearer ${data.session.access_token}`;
|
||||
return axios(originalRequest);
|
||||
}
|
||||
} catch (refreshError) {
|
||||
console.error('Failed to refresh token:', refreshError);
|
||||
// 重定向到登录页面
|
||||
if (window.location.pathname !== '/login') {
|
||||
window.location.href = '/login';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Promise.reject(error);
|
||||
}
|
||||
);
|
||||
|
||||
// Auth API - 不再需要大部分方法,因为现在直接使用 Supabase
|
||||
export const authApi = {
|
||||
// 保留 verify 方法用于与后端验证
|
||||
verify: async (): Promise<AxiosResponse> => {
|
||||
const { data } = await supabase.auth.getSession();
|
||||
const session = data.session;
|
||||
|
||||
if (!session) {
|
||||
throw new Error('No active session');
|
||||
}
|
||||
|
||||
return apiClient.get('/api/auth/verify', {
|
||||
headers: {
|
||||
Authorization: `Bearer ${session.access_token}`
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Comments API
|
||||
export const commentsApi = {
|
||||
getComments: (params?: Record<string, string | number | boolean>): Promise<AxiosResponse> =>
|
||||
apiClient.get('/api/comments', { params }),
|
||||
getComment: (id: string): Promise<AxiosResponse> =>
|
||||
apiClient.get(`/api/comments/${id}`),
|
||||
createComment: (data: Record<string, unknown>): Promise<AxiosResponse> =>
|
||||
apiClient.post('/api/comments', data),
|
||||
updateComment: (id: string, data: Record<string, unknown>): Promise<AxiosResponse> =>
|
||||
apiClient.put(`/api/comments/${id}`, data),
|
||||
deleteComment: (id: string): Promise<AxiosResponse> =>
|
||||
apiClient.delete(`/api/comments/${id}`),
|
||||
};
|
||||
|
||||
// Posts API
|
||||
export const postsApi = {
|
||||
getPosts: (params?: Record<string, string | number | boolean>): Promise<AxiosResponse> =>
|
||||
apiClient.get('/api/posts', { params }),
|
||||
getPost: (id: string): Promise<AxiosResponse> =>
|
||||
apiClient.get(`/api/posts/${id}`),
|
||||
createPost: (data: Record<string, unknown>): Promise<AxiosResponse> =>
|
||||
apiClient.post('/api/posts', data),
|
||||
updatePost: (id: string, data: Record<string, unknown>): Promise<AxiosResponse> =>
|
||||
apiClient.put(`/api/posts/${id}`, data),
|
||||
deletePost: (id: string): Promise<AxiosResponse> =>
|
||||
apiClient.delete(`/api/posts/${id}`),
|
||||
};
|
||||
|
||||
// Analytics API
|
||||
export const analyticsApi = {
|
||||
getPlatforms: (timeRange: string): Promise<AxiosResponse> =>
|
||||
apiClient.get(`/api/analytics/platforms?timeRange=${timeRange}`),
|
||||
getTimeline: (timeRange: string): Promise<AxiosResponse> =>
|
||||
apiClient.get(`/api/analytics/timeline?timeRange=${timeRange}`),
|
||||
getSentiment: (timeRange: string): Promise<AxiosResponse> =>
|
||||
apiClient.get(`/api/analytics/sentiment?timeRange=${timeRange}`),
|
||||
getStatus: (timeRange: string): Promise<AxiosResponse> =>
|
||||
apiClient.get(`/api/analytics/status?timeRange=${timeRange}`),
|
||||
getPopularContent: (timeRange: string): Promise<AxiosResponse> =>
|
||||
apiClient.get(`/api/analytics/popular-content?timeRange=${timeRange}`),
|
||||
getInfluencers: (timeRange: string): Promise<AxiosResponse> =>
|
||||
apiClient.get(`/api/analytics/influencers?timeRange=${timeRange}`),
|
||||
getConversion: (timeRange: string): Promise<AxiosResponse> =>
|
||||
apiClient.get(`/api/analytics/conversion?timeRange=${timeRange}`),
|
||||
};
|
||||
|
||||
// Templates API
|
||||
export const templatesApi = {
|
||||
getTemplates: (): Promise<AxiosResponse> =>
|
||||
apiClient.get('/api/reply-templates'),
|
||||
getTemplate: (id: string): Promise<AxiosResponse> =>
|
||||
apiClient.get(`/api/reply-templates/${id}`),
|
||||
createTemplate: (data: Record<string, unknown>): Promise<AxiosResponse> =>
|
||||
apiClient.post('/api/reply-templates', data),
|
||||
updateTemplate: (id: string, data: Record<string, unknown>): Promise<AxiosResponse> =>
|
||||
apiClient.put(`/api/reply-templates/${id}`, data),
|
||||
deleteTemplate: (id: string): Promise<AxiosResponse> =>
|
||||
apiClient.delete(`/api/reply-templates/${id}`),
|
||||
};
|
||||
|
||||
export default apiClient;
|
||||
@@ -1,8 +1,8 @@
|
||||
import { createClient } from '@supabase/supabase-js';
|
||||
|
||||
// 使用环境变量或直接使用 URL 和 Key(生产环境中应使用环境变量)
|
||||
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL || 'http://your-supabase-url';
|
||||
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY || 'your-supabase-anon-key';
|
||||
const supabaseUrl = import.meta.env.VITE_SUPABASE_URL || '';
|
||||
const supabaseAnonKey = import.meta.env.VITE_SUPABASE_ANON_KEY || '';
|
||||
|
||||
// 创建 Supabase 客户端
|
||||
const supabase = createClient(supabaseUrl, supabaseAnonKey);
|
||||
|
||||
Reference in New Issue
Block a user