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 || '--'} +
+
+
+
Follower Gender
+
-
-
{selectedCreator?.products || '--'}
-
Products
+
+
Follower Age
+
-
-
{selectedCreator?.brand_collaborations || '--'}
-
Brand Collaborations
-
-
-
{selectedCreator?.product_price || '--'}
-
Product Price
+
+
Top 5 Locations
+
-
-
- 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 || '--'} -
-
-
-
Follower Gender
- -
-
-
Follower Age
- -
-
-
Top 5 Locations
- -
-
-
-
-
- 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%;