diff --git a/src/App.jsx b/src/App.jsx
index bbd1e4a..e6df4dd 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -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);
diff --git a/src/pages/CreatorDetail.jsx b/src/pages/CreatorDetail.jsx
index 8f73578..ef75f9f 100644
--- a/src/pages/CreatorDetail.jsx
+++ b/src/pages/CreatorDetail.jsx
@@ -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 (
+
+
+
Collaboration Metrics
+
+
+ {selectedCreator?.metricsData?.collaboration_metrics?.avg_commission_rate || '--'}
+
+
Avg. Commission Rate
+
+
+
+ {selectedCreator?.metricsData?.collaboration_metrics?.products_count || '--'}
+
+
Products
+
+
+
+ {selectedCreator?.metricsData?.collaboration_metrics?.brand_collaborations || '--'}
+
+
Brand Collaborations
+
+
+
+ {selectedCreator?.metricsData?.collaboration_metrics?.product_price || '--'}
+
+
Product Price
+
+
+
+
+ Video{selectedCreator?.metricsData?.video?.date_range || '--'}
+
+
+
{selectedCreator?.metricsData?.video?.gpm || '--'}
+
Video GPM
+
+
+
{selectedCreator?.metricsData?.video?.videos_count || '--'}
+
Videos
+
+
+
{selectedCreator?.metricsData?.video?.avg_views || '--'}
+
Avg. Video Views
+
+
+
{selectedCreator?.metricsData?.video?.avg_engagement || '--'}
+
Avg. Video Engagement
+
+
+
{selectedCreator?.metricsData?.video?.avg_likes || '--'}
+
Avg. Video Likes
+
+
+
+
+ Shoppable Video
+
+ {selectedCreator?.metricsData?.shoppable_video?.date_range || '--'}
+
+
+
+
{selectedCreator?.metricsData?.shoppable_video?.gpm || '--'}
+
Video GPM
+
+
+
{selectedCreator?.metricsData?.shoppable_video?.videos_count || '--'}
+
Videos
+
+
+
{selectedCreator?.metricsData?.shoppable_video?.avg_views || '--'}
+
Avg. Video Views
+
+
+
{selectedCreator?.metricsData?.shoppable_video?.avg_engagement || '--'}
+
Avg. Video Engagement
+
+
+
{selectedCreator?.metricsData?.shoppable_video?.avg_likes || '--'}
+
Avg. Video Likes
+
+
+
+
+ LIVE{selectedCreator?.metricsData?.live?.date_range || '--'}
+
+
+
{selectedCreator?.metricsData?.live?.gpm || '--'}
+
LIVE GPM
+
+
+
{selectedCreator?.metricsData?.live?.lives_count || '--'}
+
LIVE Videos
+
+
+
{selectedCreator?.metricsData?.live?.avg_views || '--'}
+
Avg. LIVE Views
+
+
+
{selectedCreator?.metricsData?.live?.avg_engagement || '--'}
+
Avg. LIVE Engagement
+
+
+
{selectedCreator?.metricsData?.live?.avg_likes || '--'}
+
Avg. LIVE Likes
+
+
+
+
+ Shoppable LIVE
+
+ {selectedCreator?.metricsData?.shoppable_live?.date_range || '--'}
+
+
+
+
{selectedCreator?.metricsData?.shoppable_live?.gpm || '--'}
+
LIVE GPM
+
+
+
{selectedCreator?.metricsData?.shoppable_live?.lives_count || '--'}
+
LIVE Videos
+
+
+
{selectedCreator?.metricsData?.shoppable_live?.avg_views || '--'}
+
Avg. LIVE Views
+
+
+
{selectedCreator?.metricsData?.shoppable_live?.avg_engagement || '--'}
+
Avg. LIVE Engagement
+
+
+
{selectedCreator?.metricsData?.shoppable_live?.avg_likes || '--'}
+
Avg. LIVE Likes
+
+
+ {selectedCreator?.followerData &&
}
+ {selectedCreator?.trendsData &&
}
+ {selectedCreator?.videosData &&
}
+
+ );
+}
+
+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 (
-
-
-
Collaboration Metrics
-
-
{selectedCreator?.avg_commission_rate || '--'}
-
Avg. Commission Rate
+
+
+ Follwers{selectedCreator?.date_range || '--'}
+
+
+
-
-
{selectedCreator?.products || '--'}
-
Products
+
-
-
{selectedCreator?.brand_collaborations || '--'}
-
Brand Collaborations
-
-
-
{selectedCreator?.product_price || '--'}
-
Product Price
+
-
-
- Video{selectedCreator?.videoTimeRange || '--'}
-
-
-
{selectedCreator?.avgVideoGpm || '--'}
-
Video GPM
-
-
-
{selectedCreator?.videos?.length || '--'}
-
Videos
-
-
-
{selectedCreator?.avgVideoViews || '--'}
-
Avg. Video Views
-
-
-
{selectedCreator?.avgVideoEngagements || '--'}
-
Avg. Video Engagement
-
-
-
{selectedCreator?.avgVideoLikes || '--'}
-
Avg. Video Likes
-
-
-
-
- Shoppable Video{selectedCreator?.videoTimeRange || '--'}
-
-
-
{selectedCreator?.avgVideoGpm || '--'}
-
Video GPM
-
-
-
{selectedCreator?.videos?.length || '--'}
-
Videos
-
-
-
{selectedCreator?.avgVideoViews || '--'}
-
Avg. Video Views
-
-
-
{selectedCreator?.avgVideoEngagements || '--'}
-
Avg. Video Engagement
-
-
-
{selectedCreator?.avgVideoLikes || '--'}
-
Avg. Video Likes
-
-
-
-
- LIVE{selectedCreator?.liveTimeRange || '--'}
-
-
-
{selectedCreator?.avgLiveGpm || '--'}
-
LIVE GPM
-
-
-
{selectedCreator?.liveVideos || '--'}
-
LIVE Videos
-
-
-
{selectedCreator?.avgLiveViews || '--'}
-
Avg. LIVE Views
-
-
-
{selectedCreator?.avgLiveEngagements || '--'}
-
Avg. LIVE Engagement
-
-
-
{selectedCreator?.avgLiveLikes || '--'}
-
Avg. LIVE Likes
-
-
-
-
- Shoppable LIVE{selectedCreator?.liveTimeRange || '--'}
-
-
-
{selectedCreator?.avgLiveGpm || '--'}
-
LIVE GPM
-
-
-
{selectedCreator?.liveVideos || '--'}
-
LIVE Videos
-
-
-
{selectedCreator?.avgLiveViews || '--'}
-
Avg. LIVE Views
-
-
-
{selectedCreator?.avgLiveEngagements || '--'}
-
Avg. LIVE Engagement
-
-
-
{selectedCreator?.avgLiveLikes || '--'}
-
Avg. LIVE Likes
-
-
-
-
- Follwers{selectedCreator?.liveTimeRange || '--'}
-
-
-
-
-
- Trends{selectedCreator?.liveTimeRange || '--'}
-
-
-
setActiveTab('gmv')}
- >
- GMV
-
-
setActiveTab('sold')}
- >
- Items Sold
-
-
setActiveTab('followers')}
- >
- Followers
-
-
setActiveTab('views')}
- >
- Video Views
-
-
-
-
-
Videos
- {selectedCreator?.videos?.length > 0 &&
- selectedCreator?.videos.map((video) => (
-
-
-
-
-
{video.title}
-
- Release Time{video.releaseTime}
-
-
-
- {video.views}
-
-
-
- {video.likes}
-
-
-
- ))}
+
+ );
+}
-
Videos with Product
- {selectedCreator?.videosWithProduct?.length > 0 &&
- selectedCreator?.videosWithProduct.map((video) => (
-
-
-
-
-
{video.title}
-
- Release Time{video.releaseTime}
-
-
-
- {video.views}
-
-
-
- {video.likes}
-
+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 (
+
+
+ Trends{selectedCreator?.liveTimeRange || '--'}
+
+
+
setActiveTab('gmv')}
+ >
+ GMV
+
+
setActiveTab('items_sold')}
+ >
+ Items Sold
+
+
setActiveTab('followers')}
+ >
+ Followers
+
+
setActiveTab('video_views')}
+ >
+ Video Views
+
+
setActiveTab('engagement_rate')}
+ >
+ Engagement Rate
+
+
+
+
+
+
+ );
+}
+function CreatorVideos({ selectedCreator }) {
+ return (
+
+
Videos
+ {selectedCreator?.videosData?.regular_videos.total > 0 &&
+ selectedCreator?.videosData.regular_videos?.videos.map((video) => (
+
+
+
+
+
+ {video.title}
+
+
+ Release Time{video.release_date}
+
+
+
+ {video.view_count}
+
+
+
+ {video.like_count}
- ))}
-
+
+ ))}
+
+
Videos with Product
+ {selectedCreator?.videosData?.product_videos?.total > 0 &&
+ selectedCreator?.videosData?.product_videos?.videos.map((video) => (
+
+
+
+
+
+ {video.title}
+
+
+ Release Time{video.release_date}
+
+
+
+ {video.view_count}
+
+
+
+ {video.like_count}
+
+
+
+ ))}
);
}
diff --git a/src/store/slices/creatorsSlice.js b/src/store/slices/creatorsSlice.js
index 55dc569..bfa71d8 100644
--- a/src/store/slices/creatorsSlice.js
+++ b/src/store/slices/creatorsSlice.js
@@ -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;
});
},
});
diff --git a/src/styles/CreatorDiscovery.scss b/src/styles/CreatorDiscovery.scss
index 504d786..0939523 100644
--- a/src/styles/CreatorDiscovery.scss
+++ b/src/styles/CreatorDiscovery.scss
@@ -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%;