mirror of
https://github.com/Funkoala14/CreatorCenter_OOIN.git
synced 2025-06-07 21:48:14 +08:00
[dev]creator detail
This commit is contained in:
parent
7549e0f47b
commit
711c4652bb
@ -4,10 +4,10 @@ import { fas } from '@fortawesome/free-solid-svg-icons';
|
||||
import Router from './router';
|
||||
import './styles/Campaign.scss';
|
||||
import '@/styles/custom-theme.scss';
|
||||
import { Chart as ChartJS, ArcElement, Tooltip, Legend, BarElement, LinearScale, CategoryScale } from 'chart.js';
|
||||
import { Chart as ChartJS, ArcElement, Tooltip, Legend, BarElement, LinearScale, CategoryScale, LineElement, PointElement } from 'chart.js';
|
||||
import NotificationBar from './components/NotificationBar';
|
||||
|
||||
ChartJS.register(ArcElement, Tooltip, Legend, BarElement, LinearScale, CategoryScale);
|
||||
ChartJS.register(ArcElement, Tooltip, Legend, BarElement, LinearScale, CategoryScale, LineElement, PointElement);
|
||||
|
||||
// Add Font Awesome icons to library
|
||||
library.add(faTiktok, fas, faYoutube, faInstagram);
|
||||
|
@ -2,22 +2,18 @@ import { ArrowLeft, Crown, Eye, Heart, Instagram, Link, Mail, MapPin } from 'luc
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Card, Table } from 'react-bootstrap';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { clearCreator, fetchCreatorDetail } from '../store/slices/creatorsSlice';
|
||||
import { Bar, Doughnut } from 'react-chartjs-2';
|
||||
import { Link as RouterLink, useNavigate, useParams } from 'react-router-dom';
|
||||
import {
|
||||
clearCreator,
|
||||
fetchCreatorDetail,
|
||||
fetchCreatorFollowers,
|
||||
fetchCreatorMetrics,
|
||||
fetchCreatorTrends,
|
||||
fetchCreatorVideos,
|
||||
} from '../store/slices/creatorsSlice';
|
||||
import { Bar, Doughnut, Line } from 'react-chartjs-2';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
|
||||
const data = {
|
||||
labels: ['Red', 'Blue', 'Green', 'Purple'],
|
||||
datasets: [
|
||||
{
|
||||
label: 'GMV',
|
||||
data: [12, 19, 5, 2],
|
||||
backgroundColor: ['rgba(217, 107, 139)', 'rgba(101, 105, 225)', 'rgba(93, 200, 179)', 'rgba(122, 87, 218)'],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const options = {
|
||||
plugins: {
|
||||
legend: {
|
||||
@ -66,11 +62,18 @@ export default function CreatorDetail({}) {
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchCreatorDetail({ creatorId: id }));
|
||||
fetchCreatorBaisInfo();
|
||||
return () => {
|
||||
dispatch(clearCreator());
|
||||
};
|
||||
}, [dispatch, id]);
|
||||
|
||||
const fetchCreatorBaisInfo = () => {
|
||||
dispatch(fetchCreatorMetrics({ creatorId: id }));
|
||||
dispatch(fetchCreatorFollowers({ creatorId: id }));
|
||||
dispatch(fetchCreatorTrends({ creatorId: id }));
|
||||
dispatch(fetchCreatorVideos({ creatorId: id }));
|
||||
};
|
||||
const processChartData = (data, chart) => {
|
||||
switch (chart) {
|
||||
case 'channel':
|
||||
@ -284,7 +287,150 @@ export default function CreatorDetail({}) {
|
||||
}
|
||||
|
||||
function CreatorBasicInfo({ selectedCreator }) {
|
||||
const [activeTab, setActiveTab] = useState('gmv');
|
||||
useEffect(() => {}, [selectedCreator]);
|
||||
return (
|
||||
<div className='creator-basic-info card'>
|
||||
<div className='basic-info-list'>
|
||||
<div className='basic-info-title'>Collaboration Metrics</div>
|
||||
<div className='basic-info-item'>
|
||||
<div className='value'>
|
||||
{selectedCreator?.metricsData?.collaboration_metrics?.avg_commission_rate || '--'}
|
||||
</div>
|
||||
<div className='name'>Avg. Commission Rate</div>
|
||||
</div>
|
||||
<div className='basic-info-item'>
|
||||
<div className='value'>
|
||||
{selectedCreator?.metricsData?.collaboration_metrics?.products_count || '--'}
|
||||
</div>
|
||||
<div className='name'>Products</div>
|
||||
</div>
|
||||
<div className='basic-info-item'>
|
||||
<div className='value'>
|
||||
{selectedCreator?.metricsData?.collaboration_metrics?.brand_collaborations || '--'}
|
||||
</div>
|
||||
<div className='name'>Brand Collaborations</div>
|
||||
</div>
|
||||
<div className='basic-info-item'>
|
||||
<div className='value'>
|
||||
{selectedCreator?.metricsData?.collaboration_metrics?.product_price || '--'}
|
||||
</div>
|
||||
<div className='name'>Product Price</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='basic-info-list'>
|
||||
<div className='basic-info-title'>
|
||||
Video<span className='time-range'>{selectedCreator?.metricsData?.video?.date_range || '--'}</span>
|
||||
</div>
|
||||
<div className='basic-info-item'>
|
||||
<div className='value'>{selectedCreator?.metricsData?.video?.gpm || '--'}</div>
|
||||
<div className='name'>Video GPM</div>
|
||||
</div>
|
||||
<div className='basic-info-item'>
|
||||
<div className='value'>{selectedCreator?.metricsData?.video?.videos_count || '--'}</div>
|
||||
<div className='name'>Videos</div>
|
||||
</div>
|
||||
<div className='basic-info-item'>
|
||||
<div className='value'>{selectedCreator?.metricsData?.video?.avg_views || '--'}</div>
|
||||
<div className='name'>Avg. Video Views</div>
|
||||
</div>
|
||||
<div className='basic-info-item'>
|
||||
<div className='value'>{selectedCreator?.metricsData?.video?.avg_engagement || '--'}</div>
|
||||
<div className='name'>Avg. Video Engagement</div>
|
||||
</div>
|
||||
<div className='basic-info-item'>
|
||||
<div className='value'>{selectedCreator?.metricsData?.video?.avg_likes || '--'}</div>
|
||||
<div className='name'>Avg. Video Likes</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='basic-info-list'>
|
||||
<div className='basic-info-title'>
|
||||
Shoppable Video
|
||||
<span className='time-range'>
|
||||
{selectedCreator?.metricsData?.shoppable_video?.date_range || '--'}
|
||||
</span>
|
||||
</div>
|
||||
<div className='basic-info-item'>
|
||||
<div className='value'>{selectedCreator?.metricsData?.shoppable_video?.gpm || '--'}</div>
|
||||
<div className='name'>Video GPM</div>
|
||||
</div>
|
||||
<div className='basic-info-item'>
|
||||
<div className='value'>{selectedCreator?.metricsData?.shoppable_video?.videos_count || '--'}</div>
|
||||
<div className='name'>Videos</div>
|
||||
</div>
|
||||
<div className='basic-info-item'>
|
||||
<div className='value'>{selectedCreator?.metricsData?.shoppable_video?.avg_views || '--'}</div>
|
||||
<div className='name'>Avg. Video Views</div>
|
||||
</div>
|
||||
<div className='basic-info-item'>
|
||||
<div className='value'>{selectedCreator?.metricsData?.shoppable_video?.avg_engagement || '--'}</div>
|
||||
<div className='name'>Avg. Video Engagement</div>
|
||||
</div>
|
||||
<div className='basic-info-item'>
|
||||
<div className='value'>{selectedCreator?.metricsData?.shoppable_video?.avg_likes || '--'}</div>
|
||||
<div className='name'>Avg. Video Likes</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='basic-info-list'>
|
||||
<div className='basic-info-title'>
|
||||
LIVE<span className='time-range'>{selectedCreator?.metricsData?.live?.date_range || '--'}</span>
|
||||
</div>
|
||||
<div className='basic-info-item'>
|
||||
<div className='value'>{selectedCreator?.metricsData?.live?.gpm || '--'}</div>
|
||||
<div className='name'>LIVE GPM</div>
|
||||
</div>
|
||||
<div className='basic-info-item'>
|
||||
<div className='value'>{selectedCreator?.metricsData?.live?.lives_count || '--'}</div>
|
||||
<div className='name'>LIVE Videos</div>
|
||||
</div>
|
||||
<div className='basic-info-item'>
|
||||
<div className='value'>{selectedCreator?.metricsData?.live?.avg_views || '--'}</div>
|
||||
<div className='name'>Avg. LIVE Views</div>
|
||||
</div>
|
||||
<div className='basic-info-item'>
|
||||
<div className='value'>{selectedCreator?.metricsData?.live?.avg_engagement || '--'}</div>
|
||||
<div className='name'>Avg. LIVE Engagement</div>
|
||||
</div>
|
||||
<div className='basic-info-item'>
|
||||
<div className='value'>{selectedCreator?.metricsData?.live?.avg_likes || '--'}</div>
|
||||
<div className='name'>Avg. LIVE Likes</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='basic-info-list'>
|
||||
<div className='basic-info-title'>
|
||||
Shoppable LIVE
|
||||
<span className='time-range'>
|
||||
{selectedCreator?.metricsData?.shoppable_live?.date_range || '--'}
|
||||
</span>
|
||||
</div>
|
||||
<div className='basic-info-item'>
|
||||
<div className='value'>{selectedCreator?.metricsData?.shoppable_live?.gpm || '--'}</div>
|
||||
<div className='name'>LIVE GPM</div>
|
||||
</div>
|
||||
<div className='basic-info-item'>
|
||||
<div className='value'>{selectedCreator?.metricsData?.shoppable_live?.lives_count || '--'}</div>
|
||||
<div className='name'>LIVE Videos</div>
|
||||
</div>
|
||||
<div className='basic-info-item'>
|
||||
<div className='value'>{selectedCreator?.metricsData?.shoppable_live?.avg_views || '--'}</div>
|
||||
<div className='name'>Avg. LIVE Views</div>
|
||||
</div>
|
||||
<div className='basic-info-item'>
|
||||
<div className='value'>{selectedCreator?.metricsData?.shoppable_live?.avg_engagement || '--'}</div>
|
||||
<div className='name'>Avg. LIVE Engagement</div>
|
||||
</div>
|
||||
<div className='basic-info-item'>
|
||||
<div className='value'>{selectedCreator?.metricsData?.shoppable_live?.avg_likes || '--'}</div>
|
||||
<div className='name'>Avg. LIVE Likes</div>
|
||||
</div>
|
||||
</div>
|
||||
{selectedCreator?.followerData && <CreatorFollowerInfo selectedCreator={selectedCreator} />}
|
||||
{selectedCreator?.trendsData && <CreatorTrends selectedCreator={selectedCreator} />}
|
||||
{selectedCreator?.videosData && <CreatorVideos selectedCreator={selectedCreator} />}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function CreatorFollowerInfo({ selectedCreator }) {
|
||||
const barOptions = {
|
||||
plugins: {
|
||||
legend: { display: false },
|
||||
@ -316,238 +462,216 @@ function CreatorBasicInfo({ selectedCreator }) {
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const barData = {
|
||||
labels: ['TX', 'FL', 'NY', 'GE', 'CA'],
|
||||
datasets: [
|
||||
{
|
||||
label: 'Top 5 Locations',
|
||||
data: [11, 8, 6, 5.5, 5],
|
||||
backgroundColor: '#6C63FF',
|
||||
borderRadius: 10,
|
||||
barPercentage: 0.6,
|
||||
},
|
||||
],
|
||||
const processChartData = (data, chart) => {
|
||||
switch (chart) {
|
||||
case 'gender':
|
||||
return {
|
||||
labels: Object.keys(data),
|
||||
datasets: [
|
||||
{
|
||||
label: 'Gender',
|
||||
data: Object.values(data),
|
||||
backgroundColor: [
|
||||
'rgba(217, 107, 139)',
|
||||
'rgba(101, 105, 225)',
|
||||
'rgba(93, 200, 179)',
|
||||
'rgba(122, 87, 218)',
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
case 'age':
|
||||
return {
|
||||
labels: Object.keys(data),
|
||||
datasets: [
|
||||
{
|
||||
label: 'Age',
|
||||
data: Object.values(data),
|
||||
backgroundColor: [
|
||||
'rgba(217, 107, 139)',
|
||||
'rgba(101, 105, 225)',
|
||||
'rgba(93, 200, 179)',
|
||||
'rgba(122, 87, 218)',
|
||||
'rgba(234, 145, 110)',
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
case 'location':
|
||||
return {
|
||||
labels: Object.keys(data),
|
||||
datasets: [
|
||||
{
|
||||
label: 'Location',
|
||||
data: Object.values(data),
|
||||
backgroundColor: [
|
||||
'rgba(217, 107, 139)',
|
||||
'rgba(101, 105, 225)',
|
||||
'rgba(93, 200, 179)',
|
||||
'rgba(122, 87, 218)',
|
||||
'rgba(234, 145, 110)',
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='creator-basic-info card'>
|
||||
<div className='basic-info-list'>
|
||||
<div className='basic-info-title'>Collaboration Metrics</div>
|
||||
<div className='basic-info-item'>
|
||||
<div className='value'>{selectedCreator?.avg_commission_rate || '--'}</div>
|
||||
<div className='name'>Avg. Commission Rate</div>
|
||||
<div className='basic-info-list'>
|
||||
<div className='basic-info-title'>
|
||||
Follwers<span className='time-range'>{selectedCreator?.date_range || '--'}</span>
|
||||
</div>
|
||||
<div className='followers-data-charts'>
|
||||
<div className='data-chart'>
|
||||
<div className='chart-title'>Follower Gender</div>
|
||||
<Doughnut
|
||||
data={processChartData(selectedCreator?.followerData?.gender, 'gender')}
|
||||
options={{ ...options, cutout: 60 }}
|
||||
/>
|
||||
</div>
|
||||
<div className='basic-info-item'>
|
||||
<div className='value'>{selectedCreator?.products || '--'}</div>
|
||||
<div className='name'>Products</div>
|
||||
<div className='data-chart'>
|
||||
<div className='chart-title'>Follower Age</div>
|
||||
<Doughnut
|
||||
data={processChartData(selectedCreator?.followerData?.age, 'age')}
|
||||
options={{ ...options, cutout: 60 }}
|
||||
/>
|
||||
</div>
|
||||
<div className='basic-info-item'>
|
||||
<div className='value'>{selectedCreator?.brand_collaborations || '--'}</div>
|
||||
<div className='name'>Brand Collaborations</div>
|
||||
</div>
|
||||
<div className='basic-info-item'>
|
||||
<div className='value'>{selectedCreator?.product_price || '--'}</div>
|
||||
<div className='name'>Product Price</div>
|
||||
<div className='data-chart'>
|
||||
<div className='chart-title'>Top 5 Locations</div>
|
||||
<Bar
|
||||
data={processChartData(selectedCreator?.followerData?.locations, 'location')}
|
||||
options={barOptions}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className='basic-info-list'>
|
||||
<div className='basic-info-title'>
|
||||
Video<span className='time-range'>{selectedCreator?.videoTimeRange || '--'}</span>
|
||||
</div>
|
||||
<div className='basic-info-item'>
|
||||
<div className='value'>{selectedCreator?.avgVideoGpm || '--'}</div>
|
||||
<div className='name'>Video GPM</div>
|
||||
</div>
|
||||
<div className='basic-info-item'>
|
||||
<div className='value'>{selectedCreator?.videos?.length || '--'}</div>
|
||||
<div className='name'>Videos</div>
|
||||
</div>
|
||||
<div className='basic-info-item'>
|
||||
<div className='value'>{selectedCreator?.avgVideoViews || '--'}</div>
|
||||
<div className='name'>Avg. Video Views</div>
|
||||
</div>
|
||||
<div className='basic-info-item'>
|
||||
<div className='value'>{selectedCreator?.avgVideoEngagements || '--'}</div>
|
||||
<div className='name'>Avg. Video Engagement</div>
|
||||
</div>
|
||||
<div className='basic-info-item'>
|
||||
<div className='value'>{selectedCreator?.avgVideoLikes || '--'}</div>
|
||||
<div className='name'>Avg. Video Likes</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='basic-info-list'>
|
||||
<div className='basic-info-title'>
|
||||
Shoppable Video<span className='time-range'>{selectedCreator?.videoTimeRange || '--'}</span>
|
||||
</div>
|
||||
<div className='basic-info-item'>
|
||||
<div className='value'>{selectedCreator?.avgVideoGpm || '--'}</div>
|
||||
<div className='name'>Video GPM</div>
|
||||
</div>
|
||||
<div className='basic-info-item'>
|
||||
<div className='value'>{selectedCreator?.videos?.length || '--'}</div>
|
||||
<div className='name'>Videos</div>
|
||||
</div>
|
||||
<div className='basic-info-item'>
|
||||
<div className='value'>{selectedCreator?.avgVideoViews || '--'}</div>
|
||||
<div className='name'>Avg. Video Views</div>
|
||||
</div>
|
||||
<div className='basic-info-item'>
|
||||
<div className='value'>{selectedCreator?.avgVideoEngagements || '--'}</div>
|
||||
<div className='name'>Avg. Video Engagement</div>
|
||||
</div>
|
||||
<div className='basic-info-item'>
|
||||
<div className='value'>{selectedCreator?.avgVideoLikes || '--'}</div>
|
||||
<div className='name'>Avg. Video Likes</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='basic-info-list'>
|
||||
<div className='basic-info-title'>
|
||||
LIVE<span className='time-range'>{selectedCreator?.liveTimeRange || '--'}</span>
|
||||
</div>
|
||||
<div className='basic-info-item'>
|
||||
<div className='value'>{selectedCreator?.avgLiveGpm || '--'}</div>
|
||||
<div className='name'>LIVE GPM</div>
|
||||
</div>
|
||||
<div className='basic-info-item'>
|
||||
<div className='value'>{selectedCreator?.liveVideos || '--'}</div>
|
||||
<div className='name'>LIVE Videos</div>
|
||||
</div>
|
||||
<div className='basic-info-item'>
|
||||
<div className='value'>{selectedCreator?.avgLiveViews || '--'}</div>
|
||||
<div className='name'>Avg. LIVE Views</div>
|
||||
</div>
|
||||
<div className='basic-info-item'>
|
||||
<div className='value'>{selectedCreator?.avgLiveEngagements || '--'}</div>
|
||||
<div className='name'>Avg. LIVE Engagement</div>
|
||||
</div>
|
||||
<div className='basic-info-item'>
|
||||
<div className='value'>{selectedCreator?.avgLiveLikes || '--'}</div>
|
||||
<div className='name'>Avg. LIVE Likes</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='basic-info-list'>
|
||||
<div className='basic-info-title'>
|
||||
Shoppable LIVE<span className='time-range'>{selectedCreator?.liveTimeRange || '--'}</span>
|
||||
</div>
|
||||
<div className='basic-info-item'>
|
||||
<div className='value'>{selectedCreator?.avgLiveGpm || '--'}</div>
|
||||
<div className='name'>LIVE GPM</div>
|
||||
</div>
|
||||
<div className='basic-info-item'>
|
||||
<div className='value'>{selectedCreator?.liveVideos || '--'}</div>
|
||||
<div className='name'>LIVE Videos</div>
|
||||
</div>
|
||||
<div className='basic-info-item'>
|
||||
<div className='value'>{selectedCreator?.avgLiveViews || '--'}</div>
|
||||
<div className='name'>Avg. LIVE Views</div>
|
||||
</div>
|
||||
<div className='basic-info-item'>
|
||||
<div className='value'>{selectedCreator?.avgLiveEngagements || '--'}</div>
|
||||
<div className='name'>Avg. LIVE Engagement</div>
|
||||
</div>
|
||||
<div className='basic-info-item'>
|
||||
<div className='value'>{selectedCreator?.avgLiveLikes || '--'}</div>
|
||||
<div className='name'>Avg. LIVE Likes</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='basic-info-list'>
|
||||
<div className='basic-info-title'>
|
||||
Follwers<span className='time-range'>{selectedCreator?.liveTimeRange || '--'}</span>
|
||||
</div>
|
||||
<div className='followers-data-charts'>
|
||||
<div className='data-chart'>
|
||||
<div className='chart-title'>Follower Gender</div>
|
||||
<Doughnut data={data} options={{ ...options, cutout: 60 }} />
|
||||
</div>
|
||||
<div className='data-chart'>
|
||||
<div className='chart-title'>Follower Age</div>
|
||||
<Doughnut data={data} options={{ ...options, cutout: 60 }} />
|
||||
</div>
|
||||
<div className='data-chart'>
|
||||
<div className='chart-title'>Top 5 Locations</div>
|
||||
<Bar data={barData} options={barOptions} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='basic-info-list'>
|
||||
<div className='basic-info-title'>
|
||||
Trends<span className='time-range'>{selectedCreator?.liveTimeRange || '--'}</span>
|
||||
</div>
|
||||
<div className='tab-switches'>
|
||||
<div
|
||||
className={`tab-switch-item ${activeTab === 'gmv' ? 'active' : ''}`}
|
||||
onClick={() => setActiveTab('gmv')}
|
||||
>
|
||||
GMV
|
||||
</div>
|
||||
<div
|
||||
className={`tab-switch-item ${activeTab === 'sold' ? 'active' : ''}`}
|
||||
onClick={() => setActiveTab('sold')}
|
||||
>
|
||||
Items Sold
|
||||
</div>
|
||||
<div
|
||||
className={`tab-switch-item ${activeTab === 'followers' ? 'active' : ''}`}
|
||||
onClick={() => setActiveTab('followers')}
|
||||
>
|
||||
Followers
|
||||
</div>
|
||||
<div
|
||||
className={`tab-switch-item ${activeTab === 'views' ? 'active' : ''}`}
|
||||
onClick={() => setActiveTab('views')}
|
||||
>
|
||||
Video Views
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className='basic-info-list video-list'>
|
||||
<div className='basic-info-title'>Videos</div>
|
||||
{selectedCreator?.videos?.length > 0 &&
|
||||
selectedCreator?.videos.map((video) => (
|
||||
<div className='basic-info-item'>
|
||||
<div className='picture' style={{ backgroundImage: `url(${video.picture})` }}></div>
|
||||
<div className='right-side-info'>
|
||||
<Crown size={16} />
|
||||
<div className='video-title'>{video.title}</div>
|
||||
<div className='release-time item-info'>
|
||||
Release Time<span className='time'>{video.releaseTime}</span>
|
||||
</div>
|
||||
<div className='views item-info'>
|
||||
<FontAwesomeIcon icon='fa-solid fa-eye' style={{ color: '#636AE8FF' }} />
|
||||
{video.views}
|
||||
</div>
|
||||
<div className='like item-info'>
|
||||
<FontAwesomeIcon icon='fa-solid fa-heart' style={{ color: '#E8618CFF' }} />
|
||||
{video.likes}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
<div className='basic-info-title'>Videos with Product</div>
|
||||
{selectedCreator?.videosWithProduct?.length > 0 &&
|
||||
selectedCreator?.videosWithProduct.map((video) => (
|
||||
<div className='basic-info-item'>
|
||||
<div className='picture' style={{ backgroundImage: `url(${video.picture})` }}></div>
|
||||
<div className='right-side-info'>
|
||||
<Crown size={16} className='crown-icon' />
|
||||
<div className='video-title'>{video.title}</div>
|
||||
<div className='release-time item-info'>
|
||||
Release Time<span className='time'>{video.releaseTime}</span>
|
||||
</div>
|
||||
<div className='views item-info'>
|
||||
<FontAwesomeIcon icon='fa-solid fa-eye' style={{ color: '#636AE8FF' }} />
|
||||
{video.views}
|
||||
</div>
|
||||
<div className='like item-info'>
|
||||
<FontAwesomeIcon icon='fa-solid fa-heart' style={{ color: '#E8618CFF' }} />
|
||||
{video.likes}
|
||||
</div>
|
||||
function CreatorTrends({ selectedCreator }) {
|
||||
const [activeTab, setActiveTab] = useState('gmv');
|
||||
|
||||
const lineOptions = {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
legend: {
|
||||
position: 'bottom',
|
||||
},
|
||||
},
|
||||
};
|
||||
const processChartData = (tab) => {
|
||||
return {
|
||||
labels: selectedCreator?.trendsData?.dates,
|
||||
datasets: [
|
||||
{
|
||||
label: tab,
|
||||
data: selectedCreator?.trendsData[tab],
|
||||
fill: false,
|
||||
borderColor: '#6366f1',
|
||||
backgroundColor: '#6366f1',
|
||||
tension: 0.4,
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='basic-info-list'>
|
||||
<div className='basic-info-title'>
|
||||
Trends<span className='time-range'>{selectedCreator?.liveTimeRange || '--'}</span>
|
||||
</div>
|
||||
<div className='tab-switches'>
|
||||
<div
|
||||
className={`tab-switch-item ${activeTab === 'gmv' ? 'active' : ''}`}
|
||||
onClick={() => setActiveTab('gmv')}
|
||||
>
|
||||
GMV
|
||||
</div>
|
||||
<div
|
||||
className={`tab-switch-item ${activeTab === 'items_sold' ? 'active' : ''}`}
|
||||
onClick={() => setActiveTab('items_sold')}
|
||||
>
|
||||
Items Sold
|
||||
</div>
|
||||
<div
|
||||
className={`tab-switch-item ${activeTab === 'followers' ? 'active' : ''}`}
|
||||
onClick={() => setActiveTab('followers')}
|
||||
>
|
||||
Followers
|
||||
</div>
|
||||
<div
|
||||
className={`tab-switch-item ${activeTab === 'video_views' ? 'active' : ''}`}
|
||||
onClick={() => setActiveTab('video_views')}
|
||||
>
|
||||
Video Views
|
||||
</div>
|
||||
<div
|
||||
className={`tab-switch-item ${activeTab === 'engagement_rate' ? 'active' : ''}`}
|
||||
onClick={() => setActiveTab('engagement_rate')}
|
||||
>
|
||||
Engagement Rate
|
||||
</div>
|
||||
</div>
|
||||
<div className='line-chart'>
|
||||
<Line data={processChartData(activeTab)} options={lineOptions} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
function CreatorVideos({ selectedCreator }) {
|
||||
return (
|
||||
<div className='basic-info-list video-list'>
|
||||
<div className='basic-info-title'>Videos</div>
|
||||
{selectedCreator?.videosData?.regular_videos.total > 0 &&
|
||||
selectedCreator?.videosData.regular_videos?.videos.map((video) => (
|
||||
<div className='basic-info-item'>
|
||||
<div className='picture' style={{ backgroundImage: `url(${video.thumbnail_url})` }}></div>
|
||||
<div className='right-side-info'>
|
||||
<Crown size={16} className='crown-icon' />
|
||||
<RouterLink to={video.video_url} className='video-title text-dark'>
|
||||
{video.title}
|
||||
</RouterLink>
|
||||
<div className='release-time item-info'>
|
||||
Release Time<span className='time'>{video.release_date}</span>
|
||||
</div>
|
||||
<div className='views item-info'>
|
||||
<FontAwesomeIcon icon='fa-solid fa-eye' style={{ color: '#636AE8FF' }} />
|
||||
{video.view_count}
|
||||
</div>
|
||||
<div className='like item-info'>
|
||||
<FontAwesomeIcon icon='fa-solid fa-heart' style={{ color: '#E8618CFF' }} />
|
||||
{video.like_count}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<div className='basic-info-title'>Videos with Product</div>
|
||||
{selectedCreator?.videosData?.product_videos?.total > 0 &&
|
||||
selectedCreator?.videosData?.product_videos?.videos.map((video) => (
|
||||
<div className='basic-info-item'>
|
||||
<div className='picture' style={{ backgroundImage: `url(${video.thumbnail_url})` }}></div>
|
||||
<div className='right-side-info'>
|
||||
<Crown size={16} className='crown-icon' />
|
||||
<RouterLink to={video.video_url} className='video-title text-dark'>
|
||||
{video.title}
|
||||
</RouterLink>
|
||||
<div className='release-time item-info'>
|
||||
Release Time<span className='time'>{video.release_date}</span>
|
||||
</div>
|
||||
<div className='views item-info'>
|
||||
<FontAwesomeIcon icon='fa-solid fa-eye' style={{ color: '#636AE8FF' }} />
|
||||
{video.view_count}
|
||||
</div>
|
||||
<div className='like item-info'>
|
||||
<FontAwesomeIcon icon='fa-solid fa-heart' style={{ color: '#E8618CFF' }} />
|
||||
{video.like_count}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -262,7 +262,7 @@ export const fetchPrivateCreators = createAsyncThunk(
|
||||
|
||||
export const fetchCreatorDetail = createAsyncThunk(
|
||||
'creators/fetchCreatorDetail',
|
||||
async ({ creatorId }, { getState, rejectWithValue }) => {
|
||||
async ({ creatorId }, { dispatch, rejectWithValue }) => {
|
||||
try {
|
||||
const response = await api.get(`/daren_detail/creators/${creatorId}`);
|
||||
if (response.code === 200) {
|
||||
@ -270,6 +270,71 @@ export const fetchCreatorDetail = createAsyncThunk(
|
||||
} else {
|
||||
throw new Error(response.message);
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('fetchCreatorDetail.rejected', error);
|
||||
return rejectWithValue(error.message);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export const fetchCreatorMetrics = createAsyncThunk(
|
||||
'creators/fetchCreatorMetrics',
|
||||
async ({ creatorId }, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await api.get(`/daren_detail/creators/${creatorId}/metrics`);
|
||||
if (response.code === 200 || response.code === 201) {
|
||||
return response;
|
||||
} else {
|
||||
throw new Error(response.message);
|
||||
}
|
||||
} catch (error) {
|
||||
return rejectWithValue(error.message);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export const fetchCreatorFollowers = createAsyncThunk(
|
||||
'creators/fetchCreatorFollowers',
|
||||
async ({ creatorId }, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await api.get(`/daren_detail/creator/${creatorId}/followers`);
|
||||
if (response.code === 200) {
|
||||
return response;
|
||||
} else {
|
||||
throw new Error(response.message);
|
||||
}
|
||||
} catch (error) {
|
||||
return rejectWithValue(error.message);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export const fetchCreatorTrends = createAsyncThunk(
|
||||
'creators/fetchCreatorTrends',
|
||||
async ({ creatorId }, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await api.get(`/daren_detail/creator/${creatorId}/trends`);
|
||||
if (response.code === 200) {
|
||||
return response;
|
||||
} else {
|
||||
throw new Error(response.message);
|
||||
}
|
||||
} catch (error) {
|
||||
return rejectWithValue(error.message);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export const fetchCreatorVideos = createAsyncThunk(
|
||||
'creators/fetchCreatorVideos',
|
||||
async ({ creatorId }, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await api.get(`/daren_detail/creator/${creatorId}/videos`);
|
||||
if (response.code === 200) {
|
||||
return response;
|
||||
} else {
|
||||
throw new Error(response.message);
|
||||
}
|
||||
} catch (error) {
|
||||
return rejectWithValue(error.message);
|
||||
}
|
||||
@ -285,7 +350,9 @@ export const addCreatorsToCampaign = createAsyncThunk(
|
||||
campaign_id: campaignId,
|
||||
});
|
||||
if (response.code === 200) {
|
||||
dispatch(setNotificationBarMessage({ message: 'Creators added to campaign successfully', type: 'success' }));
|
||||
dispatch(
|
||||
setNotificationBarMessage({ message: 'Creators added to campaign successfully', type: 'success' })
|
||||
);
|
||||
return response;
|
||||
} else {
|
||||
dispatch(setNotificationBarMessage({ message: response.message, type: 'error' }));
|
||||
@ -395,6 +462,18 @@ const creatorsSlice = createSlice({
|
||||
.addCase(fetchCreatorDetail.rejected, (state, action) => {
|
||||
state.status = 'failed';
|
||||
state.error = action.error.message;
|
||||
})
|
||||
.addCase(fetchCreatorMetrics.fulfilled, (state, action) => {
|
||||
state.selectedCreator.metricsData = action.payload.data;
|
||||
})
|
||||
.addCase(fetchCreatorFollowers.fulfilled, (state, action) => {
|
||||
state.selectedCreator.followerData = action.payload.data;
|
||||
})
|
||||
.addCase(fetchCreatorTrends.fulfilled, (state, action) => {
|
||||
state.selectedCreator.trendsData = action.payload.data;
|
||||
})
|
||||
.addCase(fetchCreatorVideos.fulfilled, (state, action) => {
|
||||
state.selectedCreator.videosData = action.payload.data;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
@ -56,12 +56,15 @@
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
gap: 1rem;
|
||||
height: 100%;
|
||||
.creator-info-detail-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: wrap;
|
||||
gap: 1rem;
|
||||
justify-content: space-between;
|
||||
height: calc(100% - 1.5rem);
|
||||
overflow-y: auto;
|
||||
|
||||
.creator-info-container {
|
||||
flex: 4;
|
||||
@ -320,6 +323,13 @@
|
||||
flex-flow: column nowrap;
|
||||
gap: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
height: 178px;
|
||||
justify-content: flex-start;
|
||||
|
||||
.crown-icon {
|
||||
margin-bottom: 0.5rem;
|
||||
color: $danger-500;
|
||||
}
|
||||
.video-title {
|
||||
font-weight: 700;
|
||||
text-overflow: ellipsis;
|
||||
@ -343,6 +353,10 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
.line-chart {
|
||||
width: 100%;
|
||||
height: 250px;
|
||||
}
|
||||
}
|
||||
.collab-info {
|
||||
width: 100%;
|
||||
|
Loading…
Reference in New Issue
Block a user