diff --git a/src/App.jsx b/src/App.jsx index 9fcdbbe..8b2347a 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -3,9 +3,10 @@ import { library } from '@fortawesome/fontawesome-svg-core'; import { fas } from '@fortawesome/free-solid-svg-icons'; import Router from './router'; import './styles/Campaign.scss'; -import { Chart as ChartJS, ArcElement, Tooltip, Legend } from "chart.js"; +import '@/styles/custom-theme.scss'; +import { Chart as ChartJS, ArcElement, Tooltip, Legend, BarElement, LinearScale, CategoryScale } from 'chart.js'; -ChartJS.register(ArcElement, Tooltip, Legend); +ChartJS.register(ArcElement, Tooltip, Legend, BarElement, LinearScale, CategoryScale); // Add Font Awesome icons to library library.add(faTiktok, fas, faYoutube, faInstagram); diff --git a/src/components/ChatDetails.jsx b/src/components/ChatDetails.jsx new file mode 100644 index 0000000..d992712 --- /dev/null +++ b/src/components/ChatDetails.jsx @@ -0,0 +1,71 @@ +import { Send, X } from 'lucide-react'; +import { useEffect, useState } from 'react'; +import { useSelector, useDispatch } from 'react-redux'; +import { selectCreator } from '../store/slices/creatorsSlice'; +import { Button, Form } from 'react-bootstrap'; + +export default function ChatDetails({ onCloseChatDetails }) { + const [search, setSearch] = useState(''); + const { selectedChat } = useSelector((state) => state.inbox); + const { selectedCreator } = useSelector((state) => state.creators); + const dispatch = useDispatch(); + + const handleSubmit = (e) => { + e.preventDefault(); + console.log('Form submitted'); + }; + + useEffect(() => { + dispatch(selectCreator(selectedChat.id)); + }, [dispatch, selectedChat]); + + return ( +
+
+
+ Chat Details +
+ +
+
+
+
+ avatar +
+
{selectedChat?.name}
+
+
+
+
+
Category
+
{selectedCreator?.category}
+
+
+
MCN
+
{selectedCreator?.mcn || '--'}
+
+
+
Pricing
+
{selectedCreator?.pricing || '--'}
+
+
+
Collab.
+
{selectedCreator?.collab || '--'}
+
+
+
+
Chat Summary
+ +
+
+
Chat Generate
+
+ + + +
+
+ ); +} diff --git a/src/components/ChatWindow.jsx b/src/components/ChatWindow.jsx index 7f2583f..b0138d5 100644 --- a/src/components/ChatWindow.jsx +++ b/src/components/ChatWindow.jsx @@ -5,7 +5,7 @@ import { Ellipsis, Send } from 'lucide-react'; import { Button, Form } from 'react-bootstrap'; import ChatInput from './ChatInput'; -export default function ChatWindow() { +export default function ChatWindow({ onOpenChatDetails }) { const { selectedChat, chatStatus } = useSelector((state) => state.inbox); const [activePlatform, setActivePlatform] = useState('email'); const dispatch = useDispatch(); @@ -45,7 +45,12 @@ export default function ChatWindow() { avatar
-
{selectedChat.name}
+
+ {selectedChat.name} +
diff --git a/src/components/CreatorList.jsx b/src/components/CreatorList.jsx index db13521..e6544f3 100644 --- a/src/components/CreatorList.jsx +++ b/src/components/CreatorList.jsx @@ -73,7 +73,7 @@ export default function CreatorList({ path, pageType = 'database' }) { if (status === 'loading') { return (
- + Loading...
diff --git a/src/components/Layouts/DividLayout.jsx b/src/components/Layouts/DividLayout.jsx index 0c374e1..5916ccd 100644 --- a/src/components/Layouts/DividLayout.jsx +++ b/src/components/Layouts/DividLayout.jsx @@ -6,8 +6,7 @@ export default function DividLayout() { return (
- -
+
diff --git a/src/components/Layouts/Sidebar.jsx b/src/components/Layouts/Sidebar.jsx index 8725b0a..fb43475 100644 --- a/src/components/Layouts/Sidebar.jsx +++ b/src/components/Layouts/Sidebar.jsx @@ -110,7 +110,7 @@ const menuItems = [ { id: 'templates', title: 'Templates', - path: '/creator-inbox/templates', + path: '/inbox-templates', icon: , }, ], diff --git a/src/components/LoadingOverlay.jsx b/src/components/LoadingOverlay.jsx new file mode 100644 index 0000000..2a0fa32 --- /dev/null +++ b/src/components/LoadingOverlay.jsx @@ -0,0 +1,26 @@ +import { Spinner } from 'react-bootstrap'; + +export default function LoadingOverlay({ status }) { + if (status !== 'loading') return null; + return ( +
+ + Loading... + +
+ ); +} +const styles = { + overlay: { + position: 'absolute', // 可改为 fixed 实现全屏 + top: 0, + left: 0, + width: '100%', + height: '100%', + backgroundColor: 'rgba(255, 255, 255, 0.6)', // 半透明背景 + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + zIndex: 9999, + }, +}; diff --git a/src/components/Spinning.jsx b/src/components/Spinning.jsx new file mode 100644 index 0000000..995dcbf --- /dev/null +++ b/src/components/Spinning.jsx @@ -0,0 +1,11 @@ +import { Spinner } from "react-bootstrap"; + +export default function Spinning() { + return ( +
+ + Loading... + +
+ ); +} diff --git a/src/components/TemplateList.jsx b/src/components/TemplateList.jsx new file mode 100644 index 0000000..9a009f0 --- /dev/null +++ b/src/components/TemplateList.jsx @@ -0,0 +1,71 @@ +import { Copy, Edit, FileText, LayoutTemplate } from 'lucide-react'; +import { Button } from 'react-bootstrap'; +import { useSelector } from 'react-redux'; +import Spinning from './Spinning'; + +export default function TemplateList({ activeTab }) { + const { templates, templatesStatus } = useSelector((state) => state.inbox); + // 如果正在加载,显示加载中 + if (templatesStatus === 'loading') { + return ; + } + if (templates.length === 0) { + return ( +
+ No templates found +
+ ); + } + return ( +
+ {templates.map((template) => ( +
+
+ + {template.type === 'initial' && ( + 初步建联 + )} + {template.type === 'bargain' && ( + 砍价邮件 + )} + {template.type === 'script' && ( + 脚本邮件 + )} + {template.type === 'cooperation' && ( + 合作追踪 + )}{' '} + - {template.name} + +
+ +
+
+
+
+ + Platform +
+
{template.platform}
+
+
+
+ + Service +
+
{template.service}
+
+
+
+ + Message +
+
{template.message}
+
+
+ ))} +
+ ); +} diff --git a/src/pages/CreatorDetail.jsx b/src/pages/CreatorDetail.jsx index aa75abc..e8671d3 100644 --- a/src/pages/CreatorDetail.jsx +++ b/src/pages/CreatorDetail.jsx @@ -1,10 +1,11 @@ -import { ArrowLeft, Instagram, Link, Mail, MapPin } from 'lucide-react'; -import { useEffect } from 'react'; -import { Card } from 'react-bootstrap'; +import { ArrowLeft, Crown, Eye, Heart, Instagram, Link, Mail, MapPin } from 'lucide-react'; +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 { selectCreator, clearCreator } from '../store/slices/creatorsSlice'; -import { Doughnut } from 'react-chartjs-2'; +import { Bar, Doughnut } from 'react-chartjs-2'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; const data = { labels: ['Red', 'Blue', 'Green', 'Purple'], @@ -18,22 +19,35 @@ const data = { }; const options = { - cutout: 70, plugins: { legend: { - position: 'bottom', + display: true, + position: 'right', + align: 'end', labels: { - generateLabels: function (chart) { - return chart.data.labels.map((label, index) => { - const value = chart.data.datasets[0].data[index]; - return { - text: `${label} ${value}%`, - fillStyle: chart.data.datasets[0].backgroundColor[index], - strokeStyle: chart.data.datasets[0].backgroundColor[index], - index: index, - }; - }); + usePointStyle: true, + pointStyle: 'circle', + boxWidth: 10, + padding: 10, + font: { + size: 14, }, + color: '#eee', + generateLabels: (chart) => { + const dataset = chart.data.datasets[0]; + return chart.data.labels.map((label, i) => ({ + text: `${label} ${dataset.data[i]}%`, + fillStyle: dataset.backgroundColor[i], + strokeStyle: dataset.backgroundColor[i], + fontColor: '#000', + lineWidth: 0, + })); + }, + }, + }, + tooltip: { + callbacks: { + label: (context) => `${context.label}: ${context.raw}%`, }, }, }, @@ -44,6 +58,7 @@ export default function CreatorDetail({}) { const navigate = useNavigate(); const dispatch = useDispatch(); const { selectedCreator } = useSelector((state) => state.creators); + const [activeTab, setActiveTab] = useState('basic'); const handleBack = () => { navigate(-1); @@ -62,7 +77,7 @@ export default function CreatorDetail({}) {
{selectedCreator ? (
-
+
{selectedCreator.name} @@ -117,7 +132,7 @@ export default function CreatorDetail({}) {
-
+
E-commerce Level
@@ -157,14 +172,36 @@ export default function CreatorDetail({}) {
GMV per sales channel
- +
GMV by product category
- +
+
+
setActiveTab('basic')} + > + Basic Info +
+
setActiveTab('deepAnalysis')} + > + Deep Analysis +
+
setActiveTab('collab')} + > + Collaboration Info +
+
+ {activeTab === 'basic' && } + {activeTab === 'collab' && }
) : (
No creator found
@@ -172,3 +209,312 @@ export default function CreatorDetail({}) {
); } + +function CreatorBasicInfo({ selectedCreator }) { + const [activeTab, setActiveTab] = useState('gmv'); + const barOptions = { + plugins: { + legend: { display: false }, + tooltip: { + callbacks: { + label: (context) => `${context.raw}%`, + }, + }, + datalabels: { + display: true, + color: '#fff', + anchor: 'end', + align: 'start', + formatter: (value) => `${value}%`, + }, + }, + scales: { + x: { + grid: { display: false }, + ticks: { + color: '#888', + font: { + size: 14, + }, + }, + }, + y: { + display: false, + }, + }, + }; + + 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, + }, + ], + }; + + return ( +
+
+
Collaboration Metrics
+
+
{selectedCreator.avgCommissionRate || '--'}
+
Avg. Commission Rate
+
+
+
{selectedCreator.products || '--'}
+
Products
+
+
+
{selectedCreator.brandCollaborations || '--'}
+
Brand Collaborations
+
+
+
{selectedCreator.productPrice || '--'}
+
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 || '--'} +
+
+
+
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 CollabInfo({ selectedCreator }) { + return ( +
+ + + + + + + + + + + + + + + {selectedCreator?.collabInfo?.length > 0 ? ( + selectedCreator?.collabInfo?.map((collab) => ( + + + + + + + + )) + ) : ( + + + + )} + +
BrandPricing DetailStart DateEnd DateStatusGMV AchievedViews AchievedVideo Link
{collab.brand}{collab.pricingDetail}{collab.startDate}{collab.endDate}{collab.status}
+ No data +
+
+ ); +} diff --git a/src/pages/CreatorInbox.jsx b/src/pages/CreatorInbox.jsx index fa09390..2aa09d8 100644 --- a/src/pages/CreatorInbox.jsx +++ b/src/pages/CreatorInbox.jsx @@ -1,12 +1,15 @@ -import React, { useEffect } from 'react'; +import React, { useEffect, useState } from 'react'; import InboxList from '@/components/InboxList'; import ChatWindow from '@/components/ChatWindow'; import '@/styles/Inbox.scss'; import { useSelector, useDispatch } from 'react-redux'; import { resetSelectedChat } from '@/store/slices/inboxSlice'; +import ChatDetails from '@/components/ChatDetails'; export default function CreatorInbox() { const { selectedChat } = useSelector((state) => state.inbox); + const [openChatDetails, setOpenChatDetails] = useState(false); + const dispatch = useDispatch(); useEffect(() => { return () => { @@ -19,7 +22,8 @@ export default function CreatorInbox() { return ( - {selectedChat?.id && } + {selectedChat?.id && setOpenChatDetails(true)} />} + {openChatDetails && setOpenChatDetails(false)} />} ); } diff --git a/src/pages/InboxTemplate.jsx b/src/pages/InboxTemplate.jsx new file mode 100644 index 0000000..0b7ef5e --- /dev/null +++ b/src/pages/InboxTemplate.jsx @@ -0,0 +1,64 @@ +import { Button } from 'react-bootstrap'; +import SearchBar from '../components/SearchBar'; +import { Plus } from 'lucide-react'; +import React, { useEffect, useState } from 'react'; +import { useDispatch } from 'react-redux'; +import { fetchTemplates } from '../store/slices/inboxSlice'; +import TemplateList from '../components/TemplateList'; + +export default function InboxTemplate() { + const [activeTab, setActiveTab] = useState('all'); + const dispatch = useDispatch(); + + useEffect(() => { + dispatch(fetchTemplates(activeTab)); + }, [dispatch, activeTab]); + + return ( + +
+ + +
+
+
Templates
+
+
+
setActiveTab('all')} + > + 全部 +
+
setActiveTab('initial')} + > + 初步建联 +
+
setActiveTab('bargain')} + > + 砍价邮件 +
+
setActiveTab('script')} + > + 脚本邮件 +
+
setActiveTab('cooperation')} + > + 合作追踪 +
+
+ +
+ ); +} diff --git a/src/router/index.jsx b/src/router/index.jsx index bd2c7af..0385e85 100644 --- a/src/router/index.jsx +++ b/src/router/index.jsx @@ -9,8 +9,9 @@ import BrandsDetail from '@/pages/BrandsDetail'; import CampaignDetail from '@/pages/CampaignDetail'; import Login from '@/pages/Login'; import CreatorDiscovery from '@/pages/CreatorDiscovery'; -import PrivateCreator from '../pages/PrivateCreator'; -import CreatorDetail from '../pages/CreatorDetail'; +import PrivateCreator from '@/pages/PrivateCreator'; +import CreatorDetail from '@/pages/CreatorDetail'; +import InboxTemplate from '@/pages/InboxTemplate'; // Routes configuration object const routes = [ @@ -88,6 +89,10 @@ const routes = [ path: '/creator/:id', element: , }, + { + path: '/inbox-templates', + element: , + }, ]; // Create router with routes wrapped in the layout @@ -108,11 +113,7 @@ const router = createBrowserRouter([ { path: '', element: , - }, - { - path: 'templates', - element: , - }, + } ], }, ]); diff --git a/src/store/slices/creatorsSlice.js b/src/store/slices/creatorsSlice.js index b1d5463..26bb99f 100644 --- a/src/store/slices/creatorsSlice.js +++ b/src/store/slices/creatorsSlice.js @@ -1,5 +1,30 @@ import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; - +const mockVideos = [ + { + id: 1, + title: 'Collagen + Biotin = your beauty routine’s new besties. For hair, skin, nails, and join...', + picture: 'https://api.dicebear.com/7.x/micah/svg?seed=1', + releaseTime: '2025-01-01', + views: 1000, + likes: 100, + }, + { + id: 2, + title: 'Collagen + Biotin = your beauty routine’s new besties. For hair, skin, nails, and join...', + picture: 'https://api.dicebear.com/7.x/micah/svg?seed=2', + releaseTime: '2025-01-01', + views: 1000, + likes: 100, + }, + { + id: 3, + title: 'Collagen + Biotin = your beauty routine’s new besties. For hair, skin, nails, and join...', + picture: 'https://api.dicebear.com/7.x/micah/svg?seed=3', + releaseTime: '2025-01-01', + views: 1000, + likes: 100, + }, +]; // 模拟创作者数据,实际项目中会从API获取 const mockCreators = [ { @@ -16,6 +41,8 @@ const mockCreators = [ hasEcommerce: true, hasTiktok: true, verified: true, + videos: mockVideos, + videosWithProduct: mockVideos, }, { id: 2, @@ -31,6 +58,8 @@ const mockCreators = [ hasEcommerce: false, hasTiktok: true, verified: false, + videos: mockVideos, + videosWithProduct: mockVideos, }, { id: 3, @@ -46,6 +75,8 @@ const mockCreators = [ hasEcommerce: true, hasTiktok: true, verified: false, + videos: mockVideos, + videosWithProduct: mockVideos, }, { id: 4, @@ -63,6 +94,8 @@ const mockCreators = [ hasInstagram: true, hasYoutube: true, verified: true, + videos: mockVideos, + videosWithProduct: mockVideos, }, { id: 5, @@ -80,6 +113,8 @@ const mockCreators = [ hasInstagram: true, hasYoutube: true, verified: true, + videos: mockVideos, + videosWithProduct: mockVideos, }, { id: 6, @@ -97,6 +132,8 @@ const mockCreators = [ hasInstagram: true, hasYoutube: true, verified: false, + videos: mockVideos, + videosWithProduct: mockVideos, }, ]; diff --git a/src/store/slices/inboxSlice.js b/src/store/slices/inboxSlice.js index f1609d0..ce8ad1a 100644 --- a/src/store/slices/inboxSlice.js +++ b/src/store/slices/inboxSlice.js @@ -1,6 +1,87 @@ import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; import { format, isToday, parseISO } from 'date-fns'; - +const mockTemplates = [ + { + id: 1, + name: 'TikTok 达播', + type: 'initial', + platform: 'TikTok', + service: '直播 - 达播', + message: `Hi {tiktok id}, + This is {sender name} from OOIN Media. We've been impressed by your amazing content on TikTok, and several of our partner brands are interested in collaborating with you on paid campaigns. + Could you please share your rate for a single TikTok video? Additionally, do you offer any bundle discounts if we're looking to collaborate on multiple videos?`, + }, + { + id: 2, + name: 'TikTok 达播', + type: 'initial', + platform: 'TikTok', + service: '直播 - 达播', + message: `Hi {tiktok id}, + This is {sender name} from OOIN Media. We've been impressed by your amazing content on TikTok, and several of our partner brands are interested in collaborating with you on paid campaigns. + Could you please share your rate for a single TikTok video? Additionally, do you offer any bundle discounts if we're looking to collaborate on multiple videos?`, + }, + { + id: 3, + name: 'TikTok 达播', + type: 'initial', + platform: 'TikTok', + service: '直播 - 达播', + message: `Hi {tiktok id}, + This is {sender name} from OOIN Media. We've been impressed by your amazing content on TikTok, and several of our partner brands are interested in collaborating with you on paid campaigns. + Could you please share your rate for a single TikTok video? Additionally, do you offer any bundle discounts if we're looking to collaborate on multiple videos?`, + }, + { + id: 4, + name: 'TikTok 达播', + type: 'initial', + platform: 'TikTok', + service: '直播 - 达播', + message: `Hi {tiktok id}, + This is {sender name} from OOIN Media. We've been impressed by your amazing content on TikTok, and several of our partner brands are interested in collaborating with you on paid campaigns. + Could you please share your rate for a single TikTok video? Additionally, do you offer any bundle discounts if we're looking to collaborate on multiple videos?`, + }, + { + id: 5, + name: 'TikTok 达播', + type: 'initial', + platform: 'TikTok', + service: '直播 - 达播', + message: `Hi {tiktok id}, + This is {sender name} from OOIN Media. We've been impressed by your amazing content on TikTok, and several of our partner brands are interested in collaborating with you on paid campaigns. + Could you please share your rate for a single TikTok video? Additionally, do you offer any bundle discounts if we're looking to collaborate on multiple videos?`, + }, + { + id: 6, + name: 'TikTok 达播', + type: 'bargain', + platform: 'TikTok', + service: '直播 - 达播', + message: `Hi {tiktok id}, + This is {sender name} from OOIN Media. We've been impressed by your amazing content on TikTok, and several of our partner brands are interested in collaborating with you on paid campaigns. + Could you please share your rate for a single TikTok video? Additionally, do you offer any bundle discounts if we're looking to collaborate on multiple videos?`, + }, + { + id: 7, + name: 'TikTok 达播', + type: 'script', + platform: 'TikTok', + service: '直播 - 达播', + message: `Hi {tiktok id}, + This is {sender name} from OOIN Media. We've been impressed by your amazing content on TikTok, and several of our partner brands are interested in collaborating with you on paid campaigns. + Could you please share your rate for a single TikTok video? Additionally, do you offer any bundle discounts if we're looking to collaborate on multiple videos?`, + }, + { + id: 8, + name: 'TikTok 达播', + type: 'cooperation', + platform: 'TikTok', + service: '直播 - 达播', + message: `Hi {tiktok id}, + This is {sender name} from OOIN Media. We've been impressed by your amazing content on TikTok, and several of our partner brands are interested in collaborating with you on paid campaigns. + Could you please share your rate for a single TikTok video? Additionally, do you offer any bundle discounts if we're looking to collaborate on multiple videos?`, + }, +]; const mockInboxList = [ { id: 1, @@ -102,12 +183,22 @@ export const fetchChatHistory = createAsyncThunk('inbox/fetchChatHistory', async return { chatHistory: mockChatHistory, chatId: id }; }); +export const fetchTemplates = createAsyncThunk('inbox/fetchTemplates', async (type) => { + await new Promise((resolve) => setTimeout(resolve, 500)); + if (type === 'all') { + return mockTemplates; + } + return mockTemplates.filter((item) => item.type === type); +}); + const initialState = { inboxList: [], inboxStatus: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed' chatStatus: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed' error: null, selectedChat: {}, + templates: [], + templatesStatus: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed' }; const inboxSlice = createSlice({ @@ -150,6 +241,17 @@ const inboxSlice = createSlice({ .addCase(fetchChatHistory.rejected, (state, action) => { state.chatStatus = 'failed'; state.error = action.error.message; + }) + .addCase(fetchTemplates.rejected, (state, action) => { + state.templatesStatus = 'failed'; + state.error = action.error.message; + }) + .addCase(fetchTemplates.pending, (state) => { + state.templatesStatus = 'loading'; + }) + .addCase(fetchTemplates.fulfilled, (state, action) => { + state.templatesStatus = 'succeeded'; + state.templates = action.payload; }); }, }); diff --git a/src/styles/CreatorDiscovery.scss b/src/styles/CreatorDiscovery.scss index 15086b6..802f161 100644 --- a/src/styles/CreatorDiscovery.scss +++ b/src/styles/CreatorDiscovery.scss @@ -56,18 +56,15 @@ .creator-info-detail-container { display: flex; flex-direction: row; + flex-wrap: wrap; gap: 1rem; justify-content: space-between; .creator-info-container { - width: 40%; + flex: 4; display: flex; flex-flow: column nowrap; - background: #ffffffff; /* white */ - border-radius: 8px; /* border-l */ - box-shadow: 0px 0px 1px #171a1f12, 0px 0px 2px #171a1f1f; /* shadow-xs */ gap: 1rem; - padding: 1rem; .creator-info-1 { display: flex; @@ -145,11 +142,8 @@ } } .creator-data { - width: 65%; - background: #ffffffff; /* white */ - border-radius: 8px; /* border-l */ - box-shadow: 0px 0px 1px #171a1f12, 0px 0px 2px #171a1f1f; /* shadow-xs */ - padding: 1rem; + padding: 1.5rem; + flex: 6; display: flex; flex-flow: column nowrap; gap: 1rem; @@ -170,7 +164,7 @@ border-radius: 1rem; background-color: $primary-100; color: $primary; - font-size: .75rem; + font-size: 0.75rem; line-height: 1.5; padding: 0.25rem 0.5rem; } @@ -179,7 +173,7 @@ .data-cards { display: grid; grid-template-columns: repeat(3, 1fr); - gap: .5rem; + gap: 0.5rem; .data-card { display: flex; flex-flow: column nowrap; @@ -200,6 +194,7 @@ .data-charts { display: flex; flex-flow: row nowrap; + gap: 1rem; .data-chart { flex: 1; .chart-title { @@ -207,12 +202,122 @@ color: $neutral-900; margin-bottom: 1rem; } - canvas { - max-width: 260px; - max-height: 260px; - } + // canvas { + // max-width: 260px; + // max-height: 260px; + // } } } } + .creator-detail-tab-switches { + } + .creator-basic-info { + padding: 1.5rem; + flex: 1; + display: flex; + flex-flow: column nowrap; + gap: 1rem; + .basic-info-list { + display: flex; + flex-flow: row wrap; + align-items: center; + justify-content: space-between; + gap: 1rem; + border-bottom: 2px solid $neutral-200; + padding-bottom: 1rem; + .basic-info-title { + font-weight: 700; + width: 100%; + font-size: 1.125rem; + display: flex; + align-items: center; + gap: 1.5rem; + .time-range { + font-size: 0.75rem; + color: $neutral-600; + } + } + .basic-info-item { + display: flex; + flex-flow: column nowrap; + align-items: center; + justify-content: center; + flex: 1; + background-color: $neutral-150; + border-radius: 6px; + padding: 1.5rem; + .value { + font-weight: 700; + } + .name { + color: $neutral-500; + font-size: 0.875rem; + } + } + + // Charts + .followers-data-charts { + display: flex; + flex-flow: row nowrap; + gap: 1rem; + width: 100%; + .data-chart { + flex: 1; + display: inline-flex; + flex-flow: column nowrap; + justify-content: space-between; + canvas { + } + } + } + } + .video-list { + .basic-info-item { + flex-flow: row nowrap; + padding: 0; + gap: 0.5rem; + background-color: transparent; + .picture { + width: 100px; + height: 178px; + background-size: cover; + background-position: center; + background-color: $neutral-200; + border-radius: 8px; + } + .right-side-info { + flex: 1; + display: flex; + flex-flow: column nowrap; + gap: 0.5rem; + font-size: .875rem; + .video-title { + font-weight: 700; + text-overflow: ellipsis; + overflow: hidden; + line-clamp: 3; + -webkit-line-clamp: 3; + -webkit-box-orient: vertical; + } + .release-time { + color: $neutral-500; + .time { + color: $neutral-900; + } + } + .item-info { + display: flex; + flex-flow: row nowrap; + gap: 0.25rem; + align-items: center; + } + } + + } + } + } + .collab-info { + width: 100%; + } } } diff --git a/src/styles/Inbox.scss b/src/styles/Inbox.scss index b4febf7..597b088 100644 --- a/src/styles/Inbox.scss +++ b/src/styles/Inbox.scss @@ -164,6 +164,7 @@ flex-flow: row nowrap; gap: 0.5rem; align-items: center; + cursor: pointer; .chat-window-header-left-avatar { width: 2.5rem; @@ -207,7 +208,7 @@ flex-grow: 1; height: 100%; overflow: hidden; - + .chat-body { padding: 1rem; overflow-y: auto; @@ -255,3 +256,168 @@ box-shadow: none; border: 1px solid #ccc; } + +.chat-details { + display: flex; + flex-flow: column nowrap; + flex-grow: 1; + background-color: #fff; + border-radius: 0.4rem; + padding: 1rem; + width: 300px; + flex-shrink: 0; + overflow-y: auto; + + .chat-details-header { + display: flex; + flex-flow: column nowrap; + font-weight: 700; + gap: 1rem; + border-bottom: 1px solid $neutral-200; + padding-bottom: 1rem; + + .chat-header-title { + display: flex; + flex-flow: row nowrap; + justify-content: space-between; + align-items: center; + .chat-detail-close-btn { + cursor: pointer; + } + } + .chat-detail-header-creator { + display: flex; + flex-flow: column nowrap; + align-items: center; + gap: 0.5rem; + .chat-detail-header-creator-avatar { + width: 115px; + height: 115px; + border-radius: 50%; + overflow: hidden; + background: $primary-150; + } + } + } + .chat-detail-header-infos { + display: flex; + flex-flow: column nowrap; + gap: 0.5rem; + padding: 1rem 0; + font-size: 0.875rem; + border-bottom: 1px solid $neutral-200; + + .chat-detail-header-info-item { + display: flex; + flex-flow: row nowrap; + .chat-detail-header-info-item-label { + color: $neutral-900; + font-weight: 700; + width: 80px; + } + } + } + .chat-detail-summary { + display: flex; + flex-flow: column nowrap; + gap: 0.5rem; + padding: 1rem 0; + border-bottom: 1px solid $neutral-200; + .chat-detail-summary-title { + font-weight: 700; + } + .chat-detail-summary-input { + background-color: $neutral-200; + border: 0 solid transparent; + border-radius: 0.25rem; + padding: 0.5rem 1rem; + } + } + .chat-detail-generate { + display: flex; + flex-flow: column nowrap; + padding: 1rem 0; + .chat-detail-generate-title { + font-weight: 700; + } + .chat-detail-generate-input { + background-color: $neutral-200; + border: 0 solid transparent; + border-radius: 0.25rem; + padding: 0.5rem 1rem; + } + .generate-form { + position: relative; + .generate-input { + } + .submit-btn { + position: absolute; + bottom: 0.5rem; + right: 0.75rem; + } + } + } +} + +.template-list { + width: 100%; + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 1rem; + margin-top: 1rem; + overflow-y: auto; + height: calc(100% - 85px); + padding-bottom: 1rem; + + .template-item { + background-color: #fff; + border-radius: 6px; + padding: 1.5rem; + display: flex; + flex-flow: column nowrap; + gap: .875rem; + box-shadow: 0px 0px 1px #171a1f12, 0px 0px 2px #171a1f1F; + max-height: 380px; + height: fit-content; + + .template-item-name { + font-size: 1.25rem; + font-weight: 700; + color: $primary; + display: flex; + flex-flow: row nowrap; + justify-content: space-between; + align-items: center; + } + .template-item-item { + display: flex; + flex-flow: row nowrap; + font-size: 0.875rem; + align-items: flex-start; + .label { + width: 95px; + font-weight: 600; + color: $neutral-600; + flex-shrink: 0; + display: flex; + flex-flow: row nowrap; + align-items: center; + gap: 8px; + } + .value { + color: $neutral-900; + font-weight: 400; + } + } + .template-platform { + .value { + font-size: 0.875rem; + background: $neutral-200; + line-height: 1.5rem; + padding: 0 6px; + border-radius: 12px; + color: $neutral-700; + } + } + } +} diff --git a/src/styles/custom-theme.scss b/src/styles/custom-theme.scss index 97be9b8..1090b2f 100644 --- a/src/styles/custom-theme.scss +++ b/src/styles/custom-theme.scss @@ -19,6 +19,7 @@ .btn-outline-primary { --bs-btn-color: #6366f1 !important; --bs-btn-hover-color: white !important; + --bs-btn-active-color: white !important; } .btn-primary-subtle { @@ -114,3 +115,11 @@ a { border-color: transparent !important; box-shadow: none !important; } + +.card { + background: #ffffffff; /* white */ + border: none; + border-radius: 8px; /* border-l */ + box-shadow: 0px 0px 1px #171a1f12, 0px 0px 2px #171a1f1f; /* shadow-xs */ + padding: 1rem; +} diff --git a/src/styles/global.scss b/src/styles/global.scss index b857c6d..52d8502 100644 --- a/src/styles/global.scss +++ b/src/styles/global.scss @@ -27,4 +27,8 @@ .back-button { cursor: pointer; +} +.btn { + display: inline-flex !important; + align-items: center; } \ No newline at end of file