mirror of
https://github.com/Funkoala14/CreatorCenter_OOIN.git
synced 2025-06-08 02:49:44 +08:00
[dev]creator detail->top info
This commit is contained in:
parent
9999e334fb
commit
cf1a9d10f6
@ -141,7 +141,7 @@ OOIN Creator Center is a React application built with Vite that allows users to
|
|||||||
### UI Components
|
### UI Components
|
||||||
- `SearchBar`: Reusable search component used across different pages
|
- `SearchBar`: Reusable search component used across different pages
|
||||||
- `DatabaseFilter`: Complex filter component for creator discovery
|
- `DatabaseFilter`: Complex filter component for creator discovery
|
||||||
- `DatabaseList`: Displays creator data in a tabular format
|
- `CreatorList`: Displays creator data in a tabular format
|
||||||
- `ChatWindow` and `ChatInput`: Used for creator communications
|
- `ChatWindow` and `ChatInput`: Used for creator communications
|
||||||
- `RangeSlider`: Custom slider component for range-based filtering
|
- `RangeSlider`: Custom slider component for range-based filtering
|
||||||
|
|
||||||
|
27
package-lock.json
generated
27
package-lock.json
generated
@ -16,12 +16,14 @@
|
|||||||
"@fortawesome/react-fontawesome": "^0.2.2",
|
"@fortawesome/react-fontawesome": "^0.2.2",
|
||||||
"@reduxjs/toolkit": "^2.8.1",
|
"@reduxjs/toolkit": "^2.8.1",
|
||||||
"bootstrap": "^5.3.3",
|
"bootstrap": "^5.3.3",
|
||||||
|
"chart.js": "^4.4.9",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"lucide-react": "^0.508.0",
|
"lucide-react": "^0.508.0",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-bootstrap": "^2.10.9",
|
"react-bootstrap": "^2.10.9",
|
||||||
"react-bootstrap-range-slider": "^3.0.8",
|
"react-bootstrap-range-slider": "^3.0.8",
|
||||||
|
"react-chartjs-2": "^5.3.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
"react-redux": "^9.2.0",
|
"react-redux": "^9.2.0",
|
||||||
"react-router-dom": "^7.6.0",
|
"react-router-dom": "^7.6.0",
|
||||||
@ -1057,6 +1059,11 @@
|
|||||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@kurkle/color": {
|
||||||
|
"version": "0.3.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz",
|
||||||
|
"integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w=="
|
||||||
|
},
|
||||||
"node_modules/@modelcontextprotocol/sdk": {
|
"node_modules/@modelcontextprotocol/sdk": {
|
||||||
"version": "1.11.1",
|
"version": "1.11.1",
|
||||||
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.11.1.tgz",
|
"resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.11.1.tgz",
|
||||||
@ -2143,6 +2150,17 @@
|
|||||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/chart.js": {
|
||||||
|
"version": "4.4.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.4.9.tgz",
|
||||||
|
"integrity": "sha512-EyZ9wWKgpAU0fLJ43YAEIF8sr5F2W3LqbS40ZJyHIner2lY14ufqv2VMp69MAiZ2rpwxEUxEhIH/0U3xyRynxg==",
|
||||||
|
"dependencies": {
|
||||||
|
"@kurkle/color": "^0.3.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"pnpm": ">=8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/chokidar": {
|
"node_modules/chokidar": {
|
||||||
"version": "4.0.3",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
|
||||||
@ -3999,6 +4017,15 @@
|
|||||||
"react-dom": ">=17.0.0"
|
"react-dom": ">=17.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-chartjs-2": {
|
||||||
|
"version": "5.3.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-5.3.0.tgz",
|
||||||
|
"integrity": "sha512-UfZZFnDsERI3c3CZGxzvNJd02SHjaSJ8kgW1djn65H1KK8rehwTjyrRKOG3VTMG8wtHZ5rgAO5oTHtHi9GCCmw==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"chart.js": "^4.1.1",
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-dom": {
|
"node_modules/react-dom": {
|
||||||
"version": "19.1.0",
|
"version": "19.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
|
||||||
|
@ -18,12 +18,14 @@
|
|||||||
"@fortawesome/react-fontawesome": "^0.2.2",
|
"@fortawesome/react-fontawesome": "^0.2.2",
|
||||||
"@reduxjs/toolkit": "^2.8.1",
|
"@reduxjs/toolkit": "^2.8.1",
|
||||||
"bootstrap": "^5.3.3",
|
"bootstrap": "^5.3.3",
|
||||||
|
"chart.js": "^4.4.9",
|
||||||
"date-fns": "^4.1.0",
|
"date-fns": "^4.1.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"lucide-react": "^0.508.0",
|
"lucide-react": "^0.508.0",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-bootstrap": "^2.10.9",
|
"react-bootstrap": "^2.10.9",
|
||||||
"react-bootstrap-range-slider": "^3.0.8",
|
"react-bootstrap-range-slider": "^3.0.8",
|
||||||
|
"react-chartjs-2": "^5.3.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
"react-redux": "^9.2.0",
|
"react-redux": "^9.2.0",
|
||||||
"react-router-dom": "^7.6.0",
|
"react-router-dom": "^7.6.0",
|
||||||
|
@ -3,6 +3,9 @@ import { library } from '@fortawesome/fontawesome-svg-core';
|
|||||||
import { fas } from '@fortawesome/free-solid-svg-icons';
|
import { fas } from '@fortawesome/free-solid-svg-icons';
|
||||||
import Router from './router';
|
import Router from './router';
|
||||||
import './styles/Campaign.scss';
|
import './styles/Campaign.scss';
|
||||||
|
import { Chart as ChartJS, ArcElement, Tooltip, Legend } from "chart.js";
|
||||||
|
|
||||||
|
ChartJS.register(ArcElement, Tooltip, Legend);
|
||||||
|
|
||||||
// Add Font Awesome icons to library
|
// Add Font Awesome icons to library
|
||||||
library.add(faTiktok, fas, faYoutube, faInstagram);
|
library.add(faTiktok, fas, faYoutube, faInstagram);
|
||||||
|
@ -11,8 +11,9 @@ import {
|
|||||||
import { setSortBy } from '../store/slices/filtersSlice';
|
import { setSortBy } from '../store/slices/filtersSlice';
|
||||||
import '../styles/DatabaseList.scss';
|
import '../styles/DatabaseList.scss';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
|
||||||
export default function DatabaseList({ path }) {
|
export default function CreatorList({ path, pageType = 'database' }) {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const { creators, status, selectedCreators } = useSelector((state) => state.creators);
|
const { creators, status, selectedCreators } = useSelector((state) => state.creators);
|
||||||
const { sortBy, sortDirection } = useSelector((state) => state.filters);
|
const { sortBy, sortDirection } = useSelector((state) => state.filters);
|
||||||
@ -20,7 +21,7 @@ export default function DatabaseList({ path }) {
|
|||||||
// 组件加载时获取创作者数据
|
// 组件加载时获取创作者数据
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (status === 'idle') {
|
if (status === 'idle') {
|
||||||
dispatch(fetchCreators({path}));
|
dispatch(fetchCreators({ path }));
|
||||||
}
|
}
|
||||||
}, [dispatch, status]);
|
}, [dispatch, status]);
|
||||||
|
|
||||||
@ -121,6 +122,13 @@ export default function DatabaseList({ path }) {
|
|||||||
<th className='views text-center' onClick={() => handleSort('avgViews')}>
|
<th className='views text-center' onClick={() => handleSort('avgViews')}>
|
||||||
Avg. Video Views {renderSortIcon('avgViews')}
|
Avg. Video Views {renderSortIcon('avgViews')}
|
||||||
</th>
|
</th>
|
||||||
|
{pageType === 'private' && (
|
||||||
|
<>
|
||||||
|
<th className='pricing text-center'>Pricing</th>
|
||||||
|
<th className='collab-count text-center'># Collab</th>
|
||||||
|
<th className='latest-collab text-center'>Latest Collab.</th>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<th className='e-commerce text-center'>E-commerce</th>
|
<th className='e-commerce text-center'>E-commerce</th>
|
||||||
<th className='profile text-center'>Profile</th>
|
<th className='profile text-center'>Profile</th>
|
||||||
</tr>
|
</tr>
|
||||||
@ -148,7 +156,9 @@ export default function DatabaseList({ path }) {
|
|||||||
<img src={creator.avatar} alt={creator.name} />
|
<img src={creator.avatar} alt={creator.name} />
|
||||||
{creator.verified && <span className='verified-badge'></span>}
|
{creator.verified && <span className='verified-badge'></span>}
|
||||||
</div>
|
</div>
|
||||||
<div className='creator-name'>{creator.name}</div>
|
<Link to={`/creator/${creator.id}`} className='creator-name'>
|
||||||
|
{creator.name}
|
||||||
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
@ -170,6 +180,13 @@ export default function DatabaseList({ path }) {
|
|||||||
<div className='small text-muted'>Items Sold: {creator.soldPercentage}</div>
|
<div className='small text-muted'>Items Sold: {creator.soldPercentage}</div>
|
||||||
</td>
|
</td>
|
||||||
<td className='text-nowrap text-center'>{creator.avgViews}</td>
|
<td className='text-nowrap text-center'>{creator.avgViews}</td>
|
||||||
|
{pageType === 'private' && (
|
||||||
|
<>
|
||||||
|
<td className='text-center'>{creator.pricing}</td>
|
||||||
|
<td className='text-center'>{creator.collabCount}</td>
|
||||||
|
<td className='text-center'>{creator.latestCollab}</td>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<td className='text-center'>
|
<td className='text-center'>
|
||||||
{creator.hasEcommerce ? <div className='colored-dot blue mx-auto'></div> : null}
|
{creator.hasEcommerce ? <div className='colored-dot blue mx-auto'></div> : null}
|
||||||
</td>
|
</td>
|
@ -9,15 +9,14 @@ import {
|
|||||||
toggleExposureRating,
|
toggleExposureRating,
|
||||||
toggleGmvRange,
|
toggleGmvRange,
|
||||||
setViewsRange,
|
setViewsRange,
|
||||||
resetFilters,
|
setPricingRange,
|
||||||
} from '../store/slices/filtersSlice';
|
} from '../store/slices/filtersSlice';
|
||||||
import { fetchCreators } from '../store/slices/creatorsSlice';
|
import { fetchCreators } from '../store/slices/creatorsSlice';
|
||||||
import '../styles/DatabaseFilter.scss';
|
import '../styles/DatabaseFilter.scss';
|
||||||
|
|
||||||
export default function DatabaseFilter({ path }) {
|
export default function DatabaseFilter({ path, pageType = 'database' }) {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
const filters = useSelector((state) => state.filters);
|
const filters = useSelector((state) => state.filters);
|
||||||
|
|
||||||
// 类别选项数据
|
// 类别选项数据
|
||||||
const categories = [
|
const categories = [
|
||||||
'Phones & Electronics',
|
'Phones & Electronics',
|
||||||
@ -46,7 +45,7 @@ export default function DatabaseFilter({ path }) {
|
|||||||
|
|
||||||
// 预定义的离散点值
|
// 预定义的离散点值
|
||||||
const discreteValues = [0, 100, 1000, 10000, 100000, 250000, 500000];
|
const discreteValues = [0, 100, 1000, 10000, 100000, 250000, 500000];
|
||||||
|
const discretePricingValues = [0, 200, 400, 600, 800, 1000, 3000];
|
||||||
// 找到最接近的离散值索引
|
// 找到最接近的离散值索引
|
||||||
const findClosestDiscreteIndex = (value) => {
|
const findClosestDiscreteIndex = (value) => {
|
||||||
let closestIndex = 0;
|
let closestIndex = 0;
|
||||||
@ -66,12 +65,16 @@ export default function DatabaseFilter({ path }) {
|
|||||||
// 本地状态用于表单控制
|
// 本地状态用于表单控制
|
||||||
const [minViews, setMinViews] = useState(filters.viewsRange[0]);
|
const [minViews, setMinViews] = useState(filters.viewsRange[0]);
|
||||||
const [maxViews, setMaxViews] = useState(filters.viewsRange[1]);
|
const [maxViews, setMaxViews] = useState(filters.viewsRange[1]);
|
||||||
|
const [minPricing, setMinPricing] = useState(filters.pricingRange[0]);
|
||||||
|
const [maxPricing, setMaxPricing] = useState(filters.pricingRange[1]);
|
||||||
|
|
||||||
// 监听Redux状态变化,更新本地表单状态
|
// 监听Redux状态变化,更新本地表单状态
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setMinViews(filters.viewsRange[0]);
|
setMinViews(filters.viewsRange[0]);
|
||||||
setMaxViews(filters.viewsRange[1]);
|
setMaxViews(filters.viewsRange[1]);
|
||||||
}, [filters.viewsRange]);
|
setMinPricing(filters.pricingRange[0]);
|
||||||
|
setMaxPricing(filters.pricingRange[1]);
|
||||||
|
}, [filters.viewsRange, filters.pricingRange]);
|
||||||
|
|
||||||
// 组件加载时获取数据
|
// 组件加载时获取数据
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -115,6 +118,22 @@ export default function DatabaseFilter({ path }) {
|
|||||||
setMaxViews(inputValue);
|
setMaxViews(inputValue);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handlePricingRangeChange = (newRange) => {
|
||||||
|
dispatch(setPricingRange(newRange));
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理min pricing input变更
|
||||||
|
const handleMinPricingChange = (e) => {
|
||||||
|
const inputValue = parseInt(e.target.value) || 0;
|
||||||
|
setMinPricing(inputValue);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理max pricing input变更
|
||||||
|
const handleMaxPricingChange = (e) => {
|
||||||
|
const inputValue = parseInt(e.target.value) || 0;
|
||||||
|
setMaxPricing(inputValue);
|
||||||
|
};
|
||||||
|
|
||||||
// 当输入框失去焦点时,转换为最接近的离散值
|
// 当输入框失去焦点时,转换为最接近的离散值
|
||||||
const handleInputBlur = (type) => {
|
const handleInputBlur = (type) => {
|
||||||
if (type === 'min') {
|
if (type === 'min') {
|
||||||
@ -140,6 +159,29 @@ export default function DatabaseFilter({ path }) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 处理min pricing input失去焦点
|
||||||
|
const handlePricingInputBlur = (type) => {
|
||||||
|
if (type === 'min') {
|
||||||
|
const closestIndex = findClosestDiscreteIndex(minPricing);
|
||||||
|
const discreteValue = discretePricingValues[closestIndex];
|
||||||
|
|
||||||
|
// 确保最小值不超过最大值
|
||||||
|
const finalValue = Math.min(discreteValue, maxPricing);
|
||||||
|
|
||||||
|
setMinPricing(finalValue);
|
||||||
|
dispatch(setPricingRange([finalValue, filters.pricingRange[1]]));
|
||||||
|
} else {
|
||||||
|
const closestIndex = findClosestDiscreteIndex(maxPricing);
|
||||||
|
const discreteValue = discretePricingValues[closestIndex];
|
||||||
|
|
||||||
|
// 确保最大值不小于最小值
|
||||||
|
const finalValue = Math.max(discreteValue, minPricing);
|
||||||
|
|
||||||
|
setMaxPricing(finalValue);
|
||||||
|
dispatch(setPricingRange([filters.pricingRange[0], finalValue]));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// 格式化显示值的函数
|
// 格式化显示值的函数
|
||||||
const formatValue = (value) => {
|
const formatValue = (value) => {
|
||||||
if (value >= 1000000) {
|
if (value >= 1000000) {
|
||||||
@ -227,7 +269,7 @@ export default function DatabaseFilter({ path }) {
|
|||||||
{/* 视频观看量筛选 */}
|
{/* 视频观看量筛选 */}
|
||||||
<div className='filter-item'>
|
<div className='filter-item'>
|
||||||
<h5 className='filter-title'>Views</h5>
|
<h5 className='filter-title'>Views</h5>
|
||||||
<div className='filter-options filter-views'>
|
<div className='filter-options filter-views filter-range-slider'>
|
||||||
<RangeSlider
|
<RangeSlider
|
||||||
min={0}
|
min={0}
|
||||||
max={500000}
|
max={500000}
|
||||||
@ -262,6 +304,47 @@ export default function DatabaseFilter({ path }) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Pricing 筛选 */}
|
||||||
|
{pageType === 'private' && (
|
||||||
|
<div className='filter-item'>
|
||||||
|
<h5 className='filter-title'>Pricing</h5>
|
||||||
|
<div className='filter-options filter-pricing filter-range-slider'>
|
||||||
|
<RangeSlider
|
||||||
|
min={0}
|
||||||
|
max={500000}
|
||||||
|
value={filters.pricingRange}
|
||||||
|
onChange={handlePricingRangeChange}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div className='range-input'>
|
||||||
|
<InputGroup>
|
||||||
|
<InputGroup.Text>
|
||||||
|
<Eye size={16} />
|
||||||
|
</InputGroup.Text>
|
||||||
|
<Form.Control
|
||||||
|
type='number'
|
||||||
|
value={minPricing}
|
||||||
|
onChange={handleMinPricingChange}
|
||||||
|
onBlur={() => handlePricingInputBlur('min')}
|
||||||
|
/>
|
||||||
|
</InputGroup>
|
||||||
|
<span>-</span>
|
||||||
|
<InputGroup>
|
||||||
|
<InputGroup.Text>
|
||||||
|
<Eye size={16} />
|
||||||
|
</InputGroup.Text>
|
||||||
|
<Form.Control
|
||||||
|
type='number'
|
||||||
|
value={maxPricing}
|
||||||
|
onChange={handleMaxPricingChange}
|
||||||
|
onBlur={() => handlePricingInputBlur('max')}
|
||||||
|
/>
|
||||||
|
</InputGroup>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</Card.Body>
|
</Card.Body>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -59,7 +59,26 @@ const menuItems = [
|
|||||||
path: '/private-creators',
|
path: '/private-creators',
|
||||||
icon: <Heart />,
|
icon: <Heart />,
|
||||||
hasSubmenu: true,
|
hasSubmenu: true,
|
||||||
submenuItems: [],
|
submenuItems: [
|
||||||
|
{
|
||||||
|
id: 'tiktok',
|
||||||
|
title: 'TikTok',
|
||||||
|
path: '/private-creators/tiktok',
|
||||||
|
icon: <FontAwesomeIcon icon='fa-brands fa-tiktok' />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'instagram',
|
||||||
|
title: 'Instagram',
|
||||||
|
path: '/private-creators/instagram',
|
||||||
|
icon: <FontAwesomeIcon icon='fa-brands fa-instagram' />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'youtube',
|
||||||
|
title: 'YouTube',
|
||||||
|
path: '/private-creators/youtube',
|
||||||
|
icon: <FontAwesomeIcon icon='fa-brands fa-youtube' />,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 'deep-analysis',
|
id: 'deep-analysis',
|
||||||
|
174
src/pages/CreatorDetail.jsx
Normal file
174
src/pages/CreatorDetail.jsx
Normal file
@ -0,0 +1,174 @@
|
|||||||
|
import { ArrowLeft, Instagram, Link, Mail, MapPin } from 'lucide-react';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { Card } 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';
|
||||||
|
|
||||||
|
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 = {
|
||||||
|
cutout: 70,
|
||||||
|
plugins: {
|
||||||
|
legend: {
|
||||||
|
position: 'bottom',
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function CreatorDetail({}) {
|
||||||
|
const { id } = useParams();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const { selectedCreator } = useSelector((state) => state.creators);
|
||||||
|
|
||||||
|
const handleBack = () => {
|
||||||
|
navigate(-1);
|
||||||
|
};
|
||||||
|
useEffect(() => {
|
||||||
|
dispatch(selectCreator(id));
|
||||||
|
return () => {
|
||||||
|
dispatch(clearCreator());
|
||||||
|
};
|
||||||
|
}, [dispatch, id]);
|
||||||
|
console.log(selectedCreator);
|
||||||
|
return (
|
||||||
|
<div className='creator-detail-page'>
|
||||||
|
<div className='back-button' onClick={handleBack}>
|
||||||
|
<ArrowLeft size={16} /> Go back
|
||||||
|
</div>
|
||||||
|
{selectedCreator ? (
|
||||||
|
<div className='creator-info-detail-container'>
|
||||||
|
<div className='creator-info-container'>
|
||||||
|
<div className='creator-info-1'>
|
||||||
|
<div className='creator-avatar'>
|
||||||
|
<img src={selectedCreator.avatar} alt={selectedCreator.name} />
|
||||||
|
</div>
|
||||||
|
<div className='creator-info-right'>
|
||||||
|
<div className='creator-name'>{selectedCreator.name}</div>
|
||||||
|
<div className='creator-desc'>{selectedCreator.description || '--'}</div>
|
||||||
|
<div className='creator-category'>{selectedCreator.category || '--'}</div>
|
||||||
|
<div className='creator-location'>
|
||||||
|
<MapPin size={16} />
|
||||||
|
{selectedCreator.location || ' --'}
|
||||||
|
</div>
|
||||||
|
<div className='creator-live-time'>{selectedCreator.liveTime || '--'}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='creator-info-2'>
|
||||||
|
<div className='creator-info-item'>
|
||||||
|
<div className='creator-info-label'>Category</div>
|
||||||
|
<div className='creator-info-value'>{selectedCreator.category}</div>
|
||||||
|
</div>
|
||||||
|
<div className='creator-info-item'>
|
||||||
|
<div className='creator-info-label'>MCN</div>
|
||||||
|
<div className='creator-info-value'>{selectedCreator.mcn || '--'}</div>
|
||||||
|
</div>
|
||||||
|
<div className='creator-info-item'>
|
||||||
|
<div className='creator-info-label'>Pricing</div>
|
||||||
|
<div className='creator-info-value'>{selectedCreator.pricing}</div>
|
||||||
|
</div>
|
||||||
|
<div className='creator-info-item'>
|
||||||
|
<div className='creator-info-label'>Collab.</div>
|
||||||
|
<div className='creator-info-value'>{selectedCreator.collab || '--'}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='creator-info-3'>
|
||||||
|
<div className='creator-info-item'>
|
||||||
|
<div className='creator-info-label mail'>
|
||||||
|
<Mail size={18} />
|
||||||
|
</div>
|
||||||
|
<div className='creator-info-value'>{selectedCreator.email || '--'}</div>
|
||||||
|
</div>
|
||||||
|
<div className='creator-info-item'>
|
||||||
|
<div className='creator-info-label social'>
|
||||||
|
<Instagram size={18} />
|
||||||
|
</div>
|
||||||
|
<div className='creator-info-value'>{selectedCreator.instagram || '--'}</div>
|
||||||
|
</div>
|
||||||
|
<div className='creator-info-item'>
|
||||||
|
<div className='creator-info-label link'>
|
||||||
|
<Link size={18} />
|
||||||
|
</div>
|
||||||
|
<div className='creator-info-value'>{selectedCreator.url || '--'}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='creator-data'>
|
||||||
|
<div className='levels'>
|
||||||
|
<div className='level-item'>
|
||||||
|
<div className='name'>E-commerce Level</div>
|
||||||
|
<div className='value'>{selectedCreator.ecommerceLevel || '--'}</div>
|
||||||
|
</div>
|
||||||
|
<div className='level-item'>
|
||||||
|
<div className='name'>Exposure Level</div>
|
||||||
|
<div className='value'>{selectedCreator.exposureLevel || '--'}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='data-cards'>
|
||||||
|
<div className='data-card'>
|
||||||
|
<div className='value'>{selectedCreator.followers || '--'}</div>
|
||||||
|
<div className='name'>Followers</div>
|
||||||
|
</div>
|
||||||
|
<div className='data-card'>
|
||||||
|
<div className='value'>{selectedCreator.gmv || '--'}</div>
|
||||||
|
<div className='name'>GMV</div>
|
||||||
|
</div>
|
||||||
|
<div className='data-card'>
|
||||||
|
<div className='value'>{selectedCreator.avgVideoViews || '--'}</div>
|
||||||
|
<div className='name'>Avg Video Views</div>
|
||||||
|
</div>
|
||||||
|
<div className='data-card'>
|
||||||
|
<div className='value'>{selectedCreator.itemsSold || '--'}</div>
|
||||||
|
<div className='name'>Items Sold</div>
|
||||||
|
</div>
|
||||||
|
<div className='data-card'>
|
||||||
|
<div className='value'>{selectedCreator.gpm || '--'}</div>
|
||||||
|
<div className='name'>GPM</div>
|
||||||
|
</div>
|
||||||
|
<div className='data-card'>
|
||||||
|
<div className='value'>{selectedCreator.gpmPerCustomer || '--'}</div>
|
||||||
|
<div className='name'>GMV per customer</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='data-charts'>
|
||||||
|
<div className='data-chart'>
|
||||||
|
<div className='chart-title'>GMV per sales channel</div>
|
||||||
|
<Doughnut data={data} options={options} />
|
||||||
|
</div>
|
||||||
|
<div className='data-chart'>
|
||||||
|
<div className='chart-title'>GMV by product category</div>
|
||||||
|
<Doughnut data={data} options={options} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div>No creator found</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import DatabaseFilter from '../components/DatabaseFilter';
|
import DatabaseFilter from '../components/DatabaseFilter';
|
||||||
import DatabaseList from '../components/DatabaseList';
|
import CreatorList from '../components/CreatorList';
|
||||||
import SearchBar from '../components/SearchBar';
|
import SearchBar from '../components/SearchBar';
|
||||||
import { Button } from 'react-bootstrap';
|
import { Button } from 'react-bootstrap';
|
||||||
export default function Database({ path }) {
|
export default function Database({ path }) {
|
||||||
@ -16,8 +16,8 @@ export default function Database({ path }) {
|
|||||||
{path === 'instagram' && <div className='breadcrumb-item'>Instagram</div>}
|
{path === 'instagram' && <div className='breadcrumb-item'>Instagram</div>}
|
||||||
{path === 'youtube' && <div className='breadcrumb-item'>YouTube</div>}
|
{path === 'youtube' && <div className='breadcrumb-item'>YouTube</div>}
|
||||||
</div>
|
</div>
|
||||||
<DatabaseFilter path={path} />
|
<DatabaseFilter path={path} pageType={'database'} />
|
||||||
<DatabaseList path={path} />
|
<CreatorList path={path} pageType={'database'} />
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
25
src/pages/PrivateCreator.jsx
Normal file
25
src/pages/PrivateCreator.jsx
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import SearchBar from '../components/SearchBar';
|
||||||
|
import { Button } from 'react-bootstrap';
|
||||||
|
import DatabaseFilter from '../components/DatabaseFilter';
|
||||||
|
import CreatorList from '../components/CreatorList';
|
||||||
|
|
||||||
|
export default function PrivateCreator({ path }) {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<React.Fragment>
|
||||||
|
<div className='function-bar'>
|
||||||
|
<SearchBar />
|
||||||
|
<Button>+ Add to Campaign</Button>
|
||||||
|
</div>
|
||||||
|
<div className='breadcrumb'>
|
||||||
|
<div className='breadcrumb-item'>Private Creators</div>
|
||||||
|
{path === 'tiktok' && <div className='breadcrumb-item'>TikTok</div>}
|
||||||
|
{path === 'instagram' && <div className='breadcrumb-item'>Instagram</div>}
|
||||||
|
{path === 'youtube' && <div className='breadcrumb-item'>YouTube</div>}
|
||||||
|
</div>
|
||||||
|
<DatabaseFilter path={path} pageType={'private'} />
|
||||||
|
<CreatorList path={path} pageType={'private'} />
|
||||||
|
</React.Fragment>
|
||||||
|
);
|
||||||
|
}
|
@ -9,6 +9,8 @@ import BrandsDetail from '@/pages/BrandsDetail';
|
|||||||
import CampaignDetail from '@/pages/CampaignDetail';
|
import CampaignDetail from '@/pages/CampaignDetail';
|
||||||
import Login from '@/pages/Login';
|
import Login from '@/pages/Login';
|
||||||
import CreatorDiscovery from '@/pages/CreatorDiscovery';
|
import CreatorDiscovery from '@/pages/CreatorDiscovery';
|
||||||
|
import PrivateCreator from '../pages/PrivateCreator';
|
||||||
|
import CreatorDetail from '../pages/CreatorDetail';
|
||||||
|
|
||||||
// Routes configuration object
|
// Routes configuration object
|
||||||
const routes = [
|
const routes = [
|
||||||
@ -25,7 +27,7 @@ const routes = [
|
|||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
element: <Database />,
|
element: <Database path='tiktok'/>,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'tiktok',
|
path: 'tiktok',
|
||||||
@ -42,8 +44,25 @@ const routes = [
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/private-creators/*',
|
path: '/private-creators',
|
||||||
element: <Home />,
|
children: [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
element: <PrivateCreator path='tiktok' />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'tiktok',
|
||||||
|
element: <PrivateCreator path='tiktok' />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'instagram',
|
||||||
|
element: <PrivateCreator path='instagram' />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'youtube',
|
||||||
|
element: <PrivateCreator path='youtube' />,
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/deep-analysis',
|
path: '/deep-analysis',
|
||||||
@ -65,6 +84,10 @@ const routes = [
|
|||||||
path: '/settings',
|
path: '/settings',
|
||||||
element: <Home />,
|
element: <Home />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/creator/:id',
|
||||||
|
element: <CreatorDetail />,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
// Create router with routes wrapped in the layout
|
// Create router with routes wrapped in the layout
|
||||||
|
@ -158,6 +158,7 @@ const initialState = {
|
|||||||
status: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed'
|
status: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed'
|
||||||
error: null,
|
error: null,
|
||||||
selectedCreators: [],
|
selectedCreators: [],
|
||||||
|
selectedCreator: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
const creatorsSlice = createSlice({
|
const creatorsSlice = createSlice({
|
||||||
@ -169,7 +170,7 @@ const creatorsSlice = createSlice({
|
|||||||
const isSelected = state.selectedCreators.includes(creatorId);
|
const isSelected = state.selectedCreators.includes(creatorId);
|
||||||
|
|
||||||
if (isSelected) {
|
if (isSelected) {
|
||||||
state.selectedCreators = state.selectedCreators.filter((id) => id !== creatorId);
|
state.selectedCreators = state.selectedCreators.filter((id) => id.toString() !== creatorId.toString());
|
||||||
} else {
|
} else {
|
||||||
state.selectedCreators.push(creatorId);
|
state.selectedCreators.push(creatorId);
|
||||||
}
|
}
|
||||||
@ -180,6 +181,13 @@ const creatorsSlice = createSlice({
|
|||||||
clearCreatorSelection: (state) => {
|
clearCreatorSelection: (state) => {
|
||||||
state.selectedCreators = [];
|
state.selectedCreators = [];
|
||||||
},
|
},
|
||||||
|
selectCreator: (state, action) => {
|
||||||
|
const id = action.payload;
|
||||||
|
state.selectedCreator = state.creators.find((creator) => creator.id.toString() === id.toString());
|
||||||
|
},
|
||||||
|
clearCreator: (state) => {
|
||||||
|
state.selectedCreator = null;
|
||||||
|
},
|
||||||
},
|
},
|
||||||
extraReducers: (builder) => {
|
extraReducers: (builder) => {
|
||||||
builder
|
builder
|
||||||
@ -198,6 +206,7 @@ const creatorsSlice = createSlice({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const { toggleCreatorSelection, selectAllCreators, clearCreatorSelection } = creatorsSlice.actions;
|
export const { toggleCreatorSelection, selectAllCreators, clearCreatorSelection, selectCreator, clearCreator } =
|
||||||
|
creatorsSlice.actions;
|
||||||
|
|
||||||
export default creatorsSlice.reducer;
|
export default creatorsSlice.reducer;
|
||||||
|
@ -6,10 +6,10 @@ const initialState = {
|
|||||||
exposureRatings: [],
|
exposureRatings: [],
|
||||||
gmvRanges: ['$5k - $25k', '$25k - $60k'],
|
gmvRanges: ['$5k - $25k', '$25k - $60k'],
|
||||||
viewsRange: [0, 100000],
|
viewsRange: [0, 100000],
|
||||||
|
pricingRange: [0, 3000],
|
||||||
sortBy: 'followers',
|
sortBy: 'followers',
|
||||||
sortDirection: 'desc',
|
sortDirection: 'desc',
|
||||||
};
|
};
|
||||||
|
|
||||||
const filtersSlice = createSlice({
|
const filtersSlice = createSlice({
|
||||||
name: 'filters',
|
name: 'filters',
|
||||||
initialState,
|
initialState,
|
||||||
@ -49,6 +49,9 @@ const filtersSlice = createSlice({
|
|||||||
setViewsRange: (state, action) => {
|
setViewsRange: (state, action) => {
|
||||||
state.viewsRange = action.payload;
|
state.viewsRange = action.payload;
|
||||||
},
|
},
|
||||||
|
setPricingRange: (state, action) => {
|
||||||
|
state.pricingRange = action.payload;
|
||||||
|
},
|
||||||
setSortBy: (state, action) => {
|
setSortBy: (state, action) => {
|
||||||
// 如果选择了当前已激活的排序项,则切换排序方向
|
// 如果选择了当前已激活的排序项,则切换排序方向
|
||||||
if (state.sortBy === action.payload) {
|
if (state.sortBy === action.payload) {
|
||||||
@ -71,6 +74,7 @@ export const {
|
|||||||
toggleExposureRating,
|
toggleExposureRating,
|
||||||
toggleGmvRange,
|
toggleGmvRange,
|
||||||
setViewsRange,
|
setViewsRange,
|
||||||
|
setPricingRange,
|
||||||
setSortBy,
|
setSortBy,
|
||||||
resetFilters,
|
resetFilters,
|
||||||
} = filtersSlice.actions;
|
} = filtersSlice.actions;
|
||||||
|
@ -51,3 +51,168 @@
|
|||||||
border: 1px solid #171a1f12;
|
border: 1px solid #171a1f12;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.creator-detail-page {
|
||||||
|
.creator-info-detail-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 1rem;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
.creator-info-container {
|
||||||
|
width: 40%;
|
||||||
|
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;
|
||||||
|
flex-flow: row nowrap;
|
||||||
|
gap: 1rem;
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 2px solid $neutral-300;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
.creator-avatar {
|
||||||
|
width: 115px;
|
||||||
|
height: 115px;
|
||||||
|
border-radius: 50%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
.creator-info-right {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column nowrap;
|
||||||
|
gap: 0.5rem;
|
||||||
|
.creator-name {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: 700;
|
||||||
|
color: $primary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.creator-info-2 {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column nowrap;
|
||||||
|
gap: 1rem;
|
||||||
|
border-bottom: 2px solid $neutral-300;
|
||||||
|
padding-bottom: 1rem;
|
||||||
|
.creator-info-item {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row nowrap;
|
||||||
|
color: $neutral-900;
|
||||||
|
|
||||||
|
.creator-info-label {
|
||||||
|
font-weight: 700;
|
||||||
|
width: 90px;
|
||||||
|
}
|
||||||
|
.creator-info-value {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.creator-info-3 {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column nowrap;
|
||||||
|
gap: 1rem;
|
||||||
|
.creator-info-item {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row nowrap;
|
||||||
|
color: $neutral-900;
|
||||||
|
gap: 0.5rem;
|
||||||
|
|
||||||
|
.creator-info-label {
|
||||||
|
font-weight: 700;
|
||||||
|
padding: 0.45rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
color: #fff;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
&.mail {
|
||||||
|
background-color: $primary-500;
|
||||||
|
}
|
||||||
|
&.social {
|
||||||
|
background-color: $secondary-500;
|
||||||
|
}
|
||||||
|
&.link {
|
||||||
|
background-color: $info-500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.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;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column nowrap;
|
||||||
|
gap: 1rem;
|
||||||
|
|
||||||
|
.levels {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row nowrap;
|
||||||
|
.level-item {
|
||||||
|
flex: 1;
|
||||||
|
text-align: start;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row nowrap;
|
||||||
|
gap: 1rem;
|
||||||
|
.name {
|
||||||
|
font-weight: 700;
|
||||||
|
}
|
||||||
|
.value {
|
||||||
|
border-radius: 1rem;
|
||||||
|
background-color: $primary-100;
|
||||||
|
color: $primary;
|
||||||
|
font-size: .75rem;
|
||||||
|
line-height: 1.5;
|
||||||
|
padding: 0.25rem 0.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.data-cards {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: .5rem;
|
||||||
|
.data-card {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column nowrap;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background-color: $neutral-150;
|
||||||
|
border-radius: 6px;
|
||||||
|
padding: 12px 0;
|
||||||
|
.value {
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1.5rem;
|
||||||
|
}
|
||||||
|
.name {
|
||||||
|
color: $neutral-500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.data-charts {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row nowrap;
|
||||||
|
.data-chart {
|
||||||
|
flex: 1;
|
||||||
|
.chart-title {
|
||||||
|
font-weight: 700;
|
||||||
|
color: $neutral-900;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
}
|
||||||
|
canvas {
|
||||||
|
max-width: 260px;
|
||||||
|
max-height: 260px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -29,7 +29,7 @@
|
|||||||
gap: 0.5rem;
|
gap: 0.5rem;
|
||||||
flex: 1;
|
flex: 1;
|
||||||
|
|
||||||
&.filter-views {
|
&.filter-range-slider {
|
||||||
flex-direction: row;
|
flex-direction: row;
|
||||||
flex-wrap: nowrap;
|
flex-wrap: nowrap;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
|
@ -36,7 +36,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.creator-name {
|
.creator-name {
|
||||||
font-weight: 500;
|
font-weight: 700;
|
||||||
|
color: #090909
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -44,7 +44,7 @@
|
|||||||
position: absolute;
|
position: absolute;
|
||||||
height: 5px;
|
height: 5px;
|
||||||
border-radius: 3px;
|
border-radius: 3px;
|
||||||
background-color: $indigo-500;
|
background-color: $primary;
|
||||||
}
|
}
|
||||||
|
|
||||||
&__steps {
|
&__steps {
|
||||||
@ -65,8 +65,8 @@
|
|||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
&.active {
|
&.active {
|
||||||
background-color: $indigo-500;
|
background-color: $primary;
|
||||||
border-color: $indigo-500;
|
border-color: $primary;
|
||||||
transform: scale(1.2);
|
transform: scale(1.2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -83,7 +83,7 @@
|
|||||||
transform: translateX(-50%);
|
transform: translateX(-50%);
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
color: white;
|
color: white;
|
||||||
background-color: $indigo-500;
|
background-color: $primary;
|
||||||
padding: 2px 6px;
|
padding: 2px 6px;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
@ -100,7 +100,7 @@
|
|||||||
height: 0;
|
height: 0;
|
||||||
border-left: 5px solid transparent;
|
border-left: 5px solid transparent;
|
||||||
border-right: 5px solid transparent;
|
border-right: 5px solid transparent;
|
||||||
border-bottom: 5px solid $indigo-500;
|
border-bottom: 5px solid $primary;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -123,13 +123,13 @@
|
|||||||
height: 20px;
|
height: 20px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
border: 2px solid $indigo-500;
|
border: 2px solid $primary;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: $indigo-500;
|
background-color: $primary;
|
||||||
transform: scale(1.1);
|
transform: scale(1.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,13 +146,13 @@
|
|||||||
height: 20px;
|
height: 20px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
border: 2px solid $indigo-500;
|
border: 2px solid $primary;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
||||||
transition: all 0.2s ease;
|
transition: all 0.2s ease;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: $indigo-500;
|
background-color: $primary;
|
||||||
transform: scale(1.1);
|
transform: scale(1.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -190,7 +190,7 @@
|
|||||||
/* For Chrome browsers */
|
/* For Chrome browsers */
|
||||||
.thumb::-webkit-slider-thumb {
|
.thumb::-webkit-slider-thumb {
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
border: 2px solid $indigo-500;
|
border: 2px solid $primary;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
box-shadow: 0 0 3px rgba(0, 0, 0, 0.2);
|
box-shadow: 0 0 3px rgba(0, 0, 0, 0.2);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
@ -204,7 +204,7 @@
|
|||||||
/* For Firefox browsers */
|
/* For Firefox browsers */
|
||||||
.thumb::-moz-range-thumb {
|
.thumb::-moz-range-thumb {
|
||||||
background-color: #fff;
|
background-color: #fff;
|
||||||
border: 2px solid $indigo-500;
|
border: 2px solid $primary;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
box-shadow: 0 0 3px rgba(0, 0, 0, 0.2);
|
box-shadow: 0 0 3px rgba(0, 0, 0, 0.2);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
@ -1,26 +1,183 @@
|
|||||||
// 主题颜色变量
|
// 主题颜色变量
|
||||||
$primary: #636AE8FF; // 靛蓝色
|
|
||||||
$secondary: #6c757d; // 灰色
|
|
||||||
$success: #198754; // 绿色
|
|
||||||
$info: #0dcaf0; // 浅蓝色
|
|
||||||
$warning: #ffc107; // 黄色
|
|
||||||
$danger: #dc3545; // 红色
|
|
||||||
$light: #f8f9fa; // 浅色
|
$light: #f8f9fa; // 浅色
|
||||||
$dark: #212529; // 深色
|
$dark: #212529; // 深色
|
||||||
|
|
||||||
// 自定义颜色变量
|
|
||||||
$primary-100: #F2F2FDFF;
|
$primary-100: #F2F2FDFF;
|
||||||
$primary-150: #E0E1FAFF;
|
$primary-150: #E0E1FAFF;
|
||||||
|
$primary-200: #CED0F8FF;
|
||||||
|
$primary-250: #BCBFF5FF;
|
||||||
|
$primary-300: #ABAEF2FF;
|
||||||
|
$primary-350: #999DF0FF;
|
||||||
|
$primary-400: #878CEDFF;
|
||||||
|
$primary-450: #757BEAFF;
|
||||||
$primary-500: #636AE8FF;
|
$primary-500: #636AE8FF;
|
||||||
$indigo-50: #eef2ff;
|
$primary-550: #4850E4FF;
|
||||||
$indigo-100: #e0e7ff;
|
$primary-600: #2C35E0FF;
|
||||||
$indigo-500: #6366f1;
|
$primary-650: #1F27CDFF;
|
||||||
$violet-50: #f5f3ff;
|
$primary-700: #1B22B1FF;
|
||||||
$violet-100: #ede9fe;
|
$primary-750: #161D96FF;
|
||||||
$violet-400: #a78bfa;
|
$primary-800: #12177AFF;
|
||||||
|
$primary-850: #0E125EFF;
|
||||||
|
$primary-900: #0A0D42FF;
|
||||||
|
$primary: #636AE8FF;
|
||||||
|
|
||||||
|
$secondary-100: #FDF1F5FF;
|
||||||
|
$secondary-150: #FBE0E8FF;
|
||||||
|
$secondary-200: #F8CEDBFF;
|
||||||
|
$secondary-250: #F5BCCEFF;
|
||||||
|
$secondary-300: #F3AAC1FF;
|
||||||
|
$secondary-350: #F098B4FF;
|
||||||
|
$secondary-400: #EE86A7FF;
|
||||||
|
$secondary-450: #EB759AFF;
|
||||||
|
$secondary-500: #E8618CFF;
|
||||||
|
$secondary-550: #E44578FF;
|
||||||
|
$secondary-600: #E02862FF;
|
||||||
|
$secondary-650: #C91D53FF;
|
||||||
|
$secondary-700: #AC1947FF;
|
||||||
|
$secondary-750: #8E143BFF;
|
||||||
|
$secondary-800: #71102FFF;
|
||||||
|
$secondary-850: #530C22FF;
|
||||||
|
$secondary-900: #360816FF;
|
||||||
|
$secondary: #E8618CFF;
|
||||||
|
|
||||||
|
$info-100: #F1F8FDFF;
|
||||||
|
$info-150: #DAECFAFF;
|
||||||
|
$info-200: #C3E1F8FF;
|
||||||
|
$info-250: #ACD5F5FF;
|
||||||
|
$info-300: #94C9F2FF;
|
||||||
|
$info-350: #7DBEEFFF;
|
||||||
|
$info-400: #66B2ECFF;
|
||||||
|
$info-450: #4FA6E9FF;
|
||||||
|
$info-500: #379AE6FF;
|
||||||
|
$info-550: #1D8DE3FF;
|
||||||
|
$info-600: #197DCAFF;
|
||||||
|
$info-650: #166DB0FF;
|
||||||
|
$info-700: #125D95FF;
|
||||||
|
$info-750: #0F4C7BFF;
|
||||||
|
$info-800: #0C3C61FF;
|
||||||
|
$info-850: #092C47FF;
|
||||||
|
$info-900: #061C2DFF;
|
||||||
|
$info: #379AE6FF;
|
||||||
|
|
||||||
|
$warning-100: #FEF9EEFF;
|
||||||
|
$warning-150: #FCF0D7FF;
|
||||||
|
$warning-200: #FAE7C0FF;
|
||||||
|
$warning-250: #F8DEA9FF;
|
||||||
|
$warning-300: #F6D491FF;
|
||||||
|
$warning-350: #F4CB7AFF;
|
||||||
|
$warning-400: #F2C263FF;
|
||||||
|
$warning-450: #F0B94BFF;
|
||||||
|
$warning-500: #EFB034FF;
|
||||||
|
$warning-550: #ECA517FF;
|
||||||
|
$warning-600: #D29211FF;
|
||||||
|
$warning-650: #B57E0FFF;
|
||||||
|
$warning-700: #98690CFF;
|
||||||
|
$warning-750: #7A550AFF;
|
||||||
|
$warning-800: #5D4108FF;
|
||||||
|
$warning-850: #402C05FF;
|
||||||
|
$warning-900: #221803FF;
|
||||||
|
$warning: #EFB034FF;
|
||||||
|
|
||||||
|
$danger-100: #FDF2F2FF;
|
||||||
|
$danger-150: #F9DBDCFF;
|
||||||
|
$danger-200: #F5C4C6FF;
|
||||||
|
$danger-250: #F1ADAF;
|
||||||
|
$danger-300: #ED9699FF;
|
||||||
|
$danger-350: #E97F83FF;
|
||||||
|
$danger-400: #E5696DFF;
|
||||||
|
$danger-450: #E25256FF;
|
||||||
|
$danger-500: #DE3B40FF;
|
||||||
|
$danger-550: #D9252BFF;
|
||||||
|
$danger-600: #C12126FF;
|
||||||
|
$danger-650: #AA1D22FF;
|
||||||
|
$danger-700: #93191DFF;
|
||||||
|
$danger-750: #7B1518FF;
|
||||||
|
$danger-800: #641114FF;
|
||||||
|
$danger-850: #4D0D0FFF;
|
||||||
|
$danger-900: #36090BFF;
|
||||||
|
$danger: #DE3B40FF;
|
||||||
|
|
||||||
|
$success-100: #EEFDF3FF;
|
||||||
|
$success-150: #D3F9E0FF;
|
||||||
|
$success-200: #B8F5CDFF;
|
||||||
|
$success-250: #9DF2B9FF;
|
||||||
|
$success-300: #82EEA6FF;
|
||||||
|
$success-350: #67EA93FF;
|
||||||
|
$success-400: #4CE77FFF;
|
||||||
|
$success-450: #31E36CFF;
|
||||||
|
$success-500: #1DD75BFF;
|
||||||
|
$success-550: #1AC052FF;
|
||||||
|
$success-600: #17A948FF;
|
||||||
|
$success-650: #14923EFF;
|
||||||
|
$success-700: #117B34FF;
|
||||||
|
$success-750: #0E642AFF;
|
||||||
|
$success-800: #0A4D20FF;
|
||||||
|
$success-850: #073517FF;
|
||||||
|
$success-900: #041E0DFF;
|
||||||
|
$success: #1DD75BFF;
|
||||||
|
|
||||||
|
$color-3-100: #EFFCFAFF;
|
||||||
|
$color-3-150: #D4F8F2FF;
|
||||||
|
$color-3-200: #BAF3EBFF;
|
||||||
|
$color-3-250: #9FEFE3FF;
|
||||||
|
$color-3-300: #84EADBFF;
|
||||||
|
$color-3-350: #69E6D3FF;
|
||||||
|
$color-3-400: #4EE1CBFF;
|
||||||
|
$color-3-450: #33DCC3FF;
|
||||||
|
$color-3-500: #22CCB2FF;
|
||||||
|
$color-3-550: #1FB7A0FF;
|
||||||
|
$color-3-600: #1BA18DFF;
|
||||||
|
$color-3-650: #188B7AFF;
|
||||||
|
$color-3-700: #147567FF;
|
||||||
|
$color-3-750: #105F53FF;
|
||||||
|
$color-3-800: #0C4940FF;
|
||||||
|
$color-3-850: #09332DFF;
|
||||||
|
$color-3-900: #051D1AFF;
|
||||||
|
$color-3: #22CCB2FF;
|
||||||
|
|
||||||
|
$color-4-100: #F5F2FDFF;
|
||||||
|
$color-4-150: #E7DFF9FF;
|
||||||
|
$color-4-200: #D8CBF5FF;
|
||||||
|
$color-4-250: #C9B8F2FF;
|
||||||
|
$color-4-300: #BBA4EEFF;
|
||||||
|
$color-4-350: #AC91EBFF;
|
||||||
|
$color-4-400: #9D7EE7FF;
|
||||||
|
$color-4-450: #8F6AE4FF;
|
||||||
|
$color-4-500: #7F55E0FF;
|
||||||
|
$color-4-550: #6D3EDCFF;
|
||||||
|
$color-4-600: #5B27D5FF;
|
||||||
|
$color-4-650: #5123BCFF;
|
||||||
|
$color-4-700: #461EA4FF;
|
||||||
|
$color-4-750: #3B198BFF;
|
||||||
|
$color-4-800: #311572FF;
|
||||||
|
$color-4-850: #261059FF;
|
||||||
|
$color-4-900: #1C0C40FF;
|
||||||
|
$color-4: #7F55E0FF;
|
||||||
|
|
||||||
|
$color-5-100: #FDF5F1FF;
|
||||||
|
$color-5-150: #FBE8E1FF;
|
||||||
|
$color-5-200: #F8DBD0FF;
|
||||||
|
$color-5-250: #F6CFBFFF;
|
||||||
|
$color-5-300: #F4C2AFFF;
|
||||||
|
$color-5-350: #F1B59EFF;
|
||||||
|
$color-5-400: #EFA98DFF;
|
||||||
|
$color-5-450: #EC9C7CFF;
|
||||||
|
$color-5-500: #EA916EFF;
|
||||||
|
$color-5-550: #E5784CFF;
|
||||||
|
$color-5-600: #E1602CFF;
|
||||||
|
$color-5-650: #CC4F1DFF;
|
||||||
|
$color-5-700: #AC4219FF;
|
||||||
|
$color-5-750: #8D3614FF;
|
||||||
|
$color-5-800: #6D2A10FF;
|
||||||
|
$color-5-850: #4D1E0BFF;
|
||||||
|
$color-5-900: #2D1206FF;
|
||||||
|
$color-5: #EA916EFF;
|
||||||
|
|
||||||
$neutral-150: #f8f9faff;
|
$neutral-150: #f8f9faff;
|
||||||
$neutral-200: #f3f4f6ff;
|
$neutral-200: #f3f4f6ff;
|
||||||
|
$neutral-300: #DEE1E6FF; /* neutral-300 */;
|
||||||
$neutral-350: #cfd2daff;
|
$neutral-350: #cfd2daff;
|
||||||
|
$neutral-500: #9095A0FF;
|
||||||
$neutral-600: #565e6cff;
|
$neutral-600: #565e6cff;
|
||||||
$neutral-700: #323842ff;
|
$neutral-700: #323842ff;
|
||||||
$neutral-900: #171a1fff;
|
$neutral-900: #171a1fff;
|
||||||
|
@ -55,7 +55,7 @@ a {
|
|||||||
.table {
|
.table {
|
||||||
border-collapse: separate;
|
border-collapse: separate;
|
||||||
border-spacing: 0;
|
border-spacing: 0;
|
||||||
|
border: 1px solid #171a1f1f;
|
||||||
th {
|
th {
|
||||||
background-color: #f8f9fa;
|
background-color: #f8f9fa;
|
||||||
border-top: none;
|
border-top: none;
|
||||||
|
@ -24,3 +24,7 @@
|
|||||||
gap: 0.25rem;
|
gap: 0.25rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.back-button {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user