mirror of
https://github.com/Funkoala14/CreatorCenter_OOIN.git
synced 2025-06-08 02:58:14 +08:00
[dev]brands' campaign page
This commit is contained in:
parent
cfb82c19b7
commit
7b0d0a109e
18
package-lock.json
generated
18
package-lock.json
generated
@ -20,7 +20,7 @@
|
|||||||
"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.1",
|
"react-bootstrap": "^2.10.9",
|
||||||
"react-bootstrap-range-slider": "^3.0.8",
|
"react-bootstrap-range-slider": "^3.0.8",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
"react-redux": "^9.2.0",
|
"react-redux": "^9.2.0",
|
||||||
@ -1814,6 +1814,11 @@
|
|||||||
"undici-types": "~6.21.0"
|
"undici-types": "~6.21.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/prop-types": {
|
||||||
|
"version": "15.7.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.14.tgz",
|
||||||
|
"integrity": "sha512-gNMvNH49DJ7OJYv+KAKn0Xp45p8PLl6zo2YnvDIbTd4J6MER2BmWN49TG7n9LvkyihINxeKW8+3bfS2yDC9dzQ=="
|
||||||
|
},
|
||||||
"node_modules/@types/react": {
|
"node_modules/@types/react": {
|
||||||
"version": "19.1.3",
|
"version": "19.1.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.3.tgz",
|
||||||
@ -3928,13 +3933,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/react-bootstrap": {
|
"node_modules/react-bootstrap": {
|
||||||
"version": "2.10.1",
|
"version": "2.10.9",
|
||||||
"resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.10.1.tgz",
|
"resolved": "https://registry.npmjs.org/react-bootstrap/-/react-bootstrap-2.10.9.tgz",
|
||||||
"integrity": "sha512-J3OpRZIvCTQK+Tg/jOkRUvpYLHMdGeU9KqFUBQrV0d/Qr/3nsINpiOJyZMWnM5SJ3ctZdhPA6eCIKpEJR3Ellg==",
|
"integrity": "sha512-TJUCuHcxdgYpOqeWmRApM/Dy0+hVsxNRFvq2aRFQuxhNi/+ivOxC5OdWIeHS3agxvzJ4Ev4nDw2ZdBl9ymd/JQ==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/runtime": "^7.22.5",
|
"@babel/runtime": "^7.24.7",
|
||||||
"@restart/hooks": "^0.4.9",
|
"@restart/hooks": "^0.4.9",
|
||||||
"@restart/ui": "^1.6.6",
|
"@restart/ui": "^1.9.4",
|
||||||
|
"@types/prop-types": "^15.7.12",
|
||||||
"@types/react-transition-group": "^4.4.6",
|
"@types/react-transition-group": "^4.4.6",
|
||||||
"classnames": "^2.3.2",
|
"classnames": "^2.3.2",
|
||||||
"dom-helpers": "^5.2.1",
|
"dom-helpers": "^5.2.1",
|
||||||
|
@ -22,7 +22,7 @@
|
|||||||
"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.1",
|
"react-bootstrap": "^2.10.9",
|
||||||
"react-bootstrap-range-slider": "^3.0.8",
|
"react-bootstrap-range-slider": "^3.0.8",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
"react-redux": "^9.2.0",
|
"react-redux": "^9.2.0",
|
||||||
|
@ -4,7 +4,7 @@ import { fetchBrands } from '../store/slices/brandsSlice';
|
|||||||
import { Card } from 'react-bootstrap';
|
import { Card } from 'react-bootstrap';
|
||||||
import { Folders, Hash, Link, Users } from 'lucide-react';
|
import { Folders, Hash, Link, Users } from 'lucide-react';
|
||||||
|
|
||||||
export default function BrandsList() {
|
export default function BrandsList({ openBrandDetail }) {
|
||||||
const brands = useSelector((state) => state.brands.brands);
|
const brands = useSelector((state) => state.brands.brands);
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
|
|
||||||
@ -14,8 +14,8 @@ export default function BrandsList() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='brands-list'>
|
<div className='brands-list'>
|
||||||
{brands.map((brand) => (
|
{brands?.length > 0 && brands.map((brand) => (
|
||||||
<div className='brand-card shadow-xs' key={brand.id}>
|
<div className='brand-card shadow-xs' key={brand.id} onClick={() => openBrandDetail(brand)}>
|
||||||
<Card.Body>
|
<Card.Body>
|
||||||
<Card.Title className='text-primary fw-bold'>
|
<Card.Title className='text-primary fw-bold'>
|
||||||
<span className='card-logo'>{brand.name.slice(0, 1)}</span>
|
<span className='card-logo'>{brand.name.slice(0, 1)}</span>
|
||||||
|
91
src/components/CampaignInfo.jsx
Normal file
91
src/components/CampaignInfo.jsx
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
import { ChartNoAxesColumnIncreasing, CircleDollarSign, Edit, Eye, Folders, Hash, Layers, Tag, TrendingUp, UserRoundCheck } from 'lucide-react';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
|
export default function CampaignInfo() {
|
||||||
|
const { selectedCampaign } = useSelector((state) => state.brands);
|
||||||
|
return (
|
||||||
|
<div className='campaign-detail-info shadow-xs'>
|
||||||
|
<div className='campaign-info-top'>
|
||||||
|
<div className='campaign-name'>{selectedCampaign.name}</div>
|
||||||
|
<div className='campaign-descp'>{selectedCampaign.description || '--'}</div>
|
||||||
|
<div className='campaign-edit'>
|
||||||
|
<Edit size={18} />
|
||||||
|
Edit
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='campaign-info-bottom'>
|
||||||
|
<div className='campaign-info-item'>
|
||||||
|
<div className='campaign-info-item-label'>
|
||||||
|
<Layers size={18} />
|
||||||
|
Service
|
||||||
|
</div>
|
||||||
|
<div className='campaign-info-item-value'>{selectedCampaign?.service || '--'}</div>
|
||||||
|
</div>
|
||||||
|
<div className='campaign-info-item'>
|
||||||
|
<div className='campaign-info-item-label'>
|
||||||
|
<Folders size={18} />
|
||||||
|
Category
|
||||||
|
</div>
|
||||||
|
<div className='campaign-info-item-value'>
|
||||||
|
{selectedCampaign?.category?.length > 0 &&
|
||||||
|
selectedCampaign.category.map((cat) => <span className='category-tag'>{cat}</span>)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='campaign-info-item'>
|
||||||
|
<div className='campaign-info-item-label'>
|
||||||
|
<UserRoundCheck size={18} />
|
||||||
|
Followers
|
||||||
|
</div>
|
||||||
|
<div className='campaign-info-item-value'>{selectedCampaign?.followers || '--'}</div>
|
||||||
|
</div>
|
||||||
|
<div className='campaign-info-item'>
|
||||||
|
<div className='campaign-info-item-label'>
|
||||||
|
<Tag size={18} />
|
||||||
|
Creator Category
|
||||||
|
</div>
|
||||||
|
<div className='campaign-info-item-value'>{selectedCampaign?.creator_category || '--'}</div>
|
||||||
|
</div>
|
||||||
|
<div className='campaign-info-item'>
|
||||||
|
<div className='campaign-info-item-label'>
|
||||||
|
<TrendingUp size={18} />
|
||||||
|
GMV
|
||||||
|
</div>
|
||||||
|
<div className='campaign-info-item-value'>{selectedCampaign?.gmv || '--'}</div>
|
||||||
|
</div>
|
||||||
|
<div className='campaign-info-item'>
|
||||||
|
<div className='campaign-info-item-label'>
|
||||||
|
<CircleDollarSign size={18} />
|
||||||
|
Pricing
|
||||||
|
</div>
|
||||||
|
<div className='campaign-info-item-value'>{selectedCampaign?.pricing || '--'}</div>
|
||||||
|
</div>
|
||||||
|
<div className='campaign-info-item'>
|
||||||
|
<div className='campaign-info-item-label'>
|
||||||
|
<ChartNoAxesColumnIncreasing size={18} />
|
||||||
|
Creator Level
|
||||||
|
</div>
|
||||||
|
<div className='campaign-info-item-value'>
|
||||||
|
{selectedCampaign?.creator_level?.length > 0 &&
|
||||||
|
selectedCampaign.creator_level.map((level) => (
|
||||||
|
<span className='creator-level-tag'>{level}</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='campaign-info-item'>
|
||||||
|
<div className='campaign-info-item-label'>
|
||||||
|
<Eye size={18} />
|
||||||
|
Views
|
||||||
|
</div>
|
||||||
|
<div className='campaign-info-item-value'>{selectedCampaign?.views || '--'}</div>
|
||||||
|
</div>
|
||||||
|
<div className='campaign-info-item'>
|
||||||
|
<div className='campaign-info-item-label'>
|
||||||
|
<Hash size={18} />
|
||||||
|
Creators
|
||||||
|
</div>
|
||||||
|
<div className='campaign-info-item-value'>{selectedCampaign?.creators || '--'}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
70
src/components/CampaignList.jsx
Normal file
70
src/components/CampaignList.jsx
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
|
import { ChartNoAxesColumnIncreasing, CircleDollarSign, Edit, Eye, Folders, Hash, Layers, Tag, TrendingUp, UserRoundCheck } from 'lucide-react';
|
||||||
|
|
||||||
|
|
||||||
|
export default function CampaignList() {
|
||||||
|
const { selectedBrand } = useSelector((state) => state.brands);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log(selectedBrand);
|
||||||
|
}, [selectedBrand]);
|
||||||
|
return (
|
||||||
|
<div className='campaigns-list'>
|
||||||
|
{selectedBrand?.campaigns?.length > 0 &&
|
||||||
|
selectedBrand.campaigns.map((campaign) => (
|
||||||
|
<div className='campaign-info'>
|
||||||
|
<Link to={`/brands/${selectedBrand.id}/campaigns/${campaign.id}`} className='campaign-title'>
|
||||||
|
{campaign.name}
|
||||||
|
</Link>
|
||||||
|
<div className='campaign-item'>
|
||||||
|
<div className='campaign-item-label'><Layers size={18} />Service</div>
|
||||||
|
<div className='campaign-item-value'>{campaign.service}</div>
|
||||||
|
</div>
|
||||||
|
<div className='campaign-item'>
|
||||||
|
<div className='campaign-item-label'>Creator Type</div>
|
||||||
|
<div className='campaign-item-value'>{campaign.creatorType}</div>
|
||||||
|
</div>
|
||||||
|
<div className='campaign-item'>
|
||||||
|
<div className='campaign-item-label'><Hash size={18} />Creators</div>
|
||||||
|
<div className='campaign-item-value'>{campaign.creators}</div>
|
||||||
|
</div>
|
||||||
|
<div className='campaign-item'>
|
||||||
|
<div className='campaign-item-label'><ChartNoAxesColumnIncreasing size={18} />Creator Level</div>
|
||||||
|
<div className='campaign-item-value'>
|
||||||
|
{campaign.creator_level &&
|
||||||
|
campaign.creator_level.map((level) => (
|
||||||
|
<span className='creator-level-tag'>{level}</span>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='campaign-item'>
|
||||||
|
<div className='campaign-item-label'><Folders size={18} />Category</div>
|
||||||
|
<div className='campaign-item-value'>
|
||||||
|
{campaign.category &&
|
||||||
|
campaign.category.map((cat) => <span className='category-tag'>{cat}</span>)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='campaign-item'>
|
||||||
|
<div className='campaign-item-label'><TrendingUp size={18} />GMV</div>
|
||||||
|
<div className='campaign-item-value'>{campaign.gmv}</div>
|
||||||
|
</div>
|
||||||
|
<div className='campaign-item'>
|
||||||
|
<div className='campaign-item-label'><UserRoundCheck size={18} />Followers</div>
|
||||||
|
<div className='campaign-item-value'>{campaign.followers}</div>
|
||||||
|
</div>
|
||||||
|
<div className='campaign-item'>
|
||||||
|
<div className='campaign-item-label'><Eye size={18} />Views</div>
|
||||||
|
<div className='campaign-item-value'>{campaign.views}</div>
|
||||||
|
</div>
|
||||||
|
<div className='campaign-item'>
|
||||||
|
<div className='campaign-item-label'><CircleDollarSign size={18} />Budget</div>
|
||||||
|
<div className='campaign-item-value'>{campaign.budget}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -10,6 +10,7 @@ import {
|
|||||||
} from '../store/slices/creatorsSlice';
|
} from '../store/slices/creatorsSlice';
|
||||||
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';
|
||||||
|
|
||||||
export default function DatabaseList({ path }) {
|
export default function DatabaseList({ path }) {
|
||||||
const dispatch = useDispatch();
|
const dispatch = useDispatch();
|
||||||
@ -175,7 +176,7 @@ export default function DatabaseList({ path }) {
|
|||||||
<td className='text-center'>
|
<td className='text-center'>
|
||||||
{creator.hasTiktok && (
|
{creator.hasTiktok && (
|
||||||
<div className='social-icon tiktok-icon mx-auto'>
|
<div className='social-icon tiktok-icon mx-auto'>
|
||||||
<i className='fab fa-tiktok'></i>
|
<FontAwesomeIcon icon='fa-brands fa-tiktok' />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</td>
|
</td>
|
||||||
|
@ -144,15 +144,15 @@ export default function Sidebar() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={`sidebar ${collapsed ? 'sidebar-collapsed' : ''}`}>
|
<div className={`sidebar ${collapsed ? 'sidebar-collapsed' : ''}`}>
|
||||||
<div className='sidebar-header p-3 d-flex align-items-center'>
|
<Link to='/' className='sidebar-header p-3 d-flex align-items-center'>
|
||||||
<img src={logo} alt='OOIN Logo' width={48} height={48} className='me-2' />
|
<img src={logo} alt='OOIN Logo' width={48} height={48} className='me-2' />
|
||||||
{!collapsed && (
|
{!collapsed && (
|
||||||
<div>
|
<div>
|
||||||
<div className='fw-bold'>OOIN Media</div>
|
<div className='text-black fw-bold'>OOIN Media</div>
|
||||||
<div className='small text-muted'>Creator Center</div>
|
<div className='small text-muted'>Creator Center</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</Link>
|
||||||
|
|
||||||
<Nav className='flex-column sidebar-nav'>
|
<Nav className='flex-column sidebar-nav'>
|
||||||
{menuItems.map((item) => {
|
{menuItems.map((item) => {
|
||||||
|
140
src/components/ProductsList.jsx
Normal file
140
src/components/ProductsList.jsx
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { Table, Form } from 'react-bootstrap';
|
||||||
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
|
import { useParams } from 'react-router-dom';
|
||||||
|
import '../styles/Products.scss';
|
||||||
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
|
||||||
|
export default function ProductsList() {
|
||||||
|
const { brandId } = useParams();
|
||||||
|
const { brands } = useSelector((state) => state.brands);
|
||||||
|
const [products, setProducts] = useState([]);
|
||||||
|
const [selectedProducts, setSelectedProducts] = useState([]);
|
||||||
|
const [sortField, setSortField] = useState(null);
|
||||||
|
const [sortDirection, setSortDirection] = useState('asc');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (brands.length > 0) {
|
||||||
|
const brand = brands.find((b) => b.id.toString() === brandId);
|
||||||
|
if (brand && brand.products) {
|
||||||
|
setProducts(brand.products);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [brands, brandId]);
|
||||||
|
|
||||||
|
const handleSort = (field) => {
|
||||||
|
if (sortField === field) {
|
||||||
|
setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc');
|
||||||
|
} else {
|
||||||
|
setSortField(field);
|
||||||
|
setSortDirection('asc');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderSortIcon = (field) => {
|
||||||
|
if (sortField !== field) return null;
|
||||||
|
return sortDirection === 'asc' ? '↑' : '↓';
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSelectAll = (e) => {
|
||||||
|
if (e.target.checked) {
|
||||||
|
setSelectedProducts(products.map((product) => product.id));
|
||||||
|
} else {
|
||||||
|
setSelectedProducts([]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSelectProduct = (productId) => {
|
||||||
|
if (selectedProducts.includes(productId)) {
|
||||||
|
setSelectedProducts(selectedProducts.filter((id) => id !== productId));
|
||||||
|
} else {
|
||||||
|
setSelectedProducts([...selectedProducts, productId]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='products-list'>
|
||||||
|
<Table responsive hover className='bg-white shadow-xs rounded overflow-hidden'>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th className='selector' style={{ width: '40px' }}>
|
||||||
|
<Form.Check
|
||||||
|
type='checkbox'
|
||||||
|
checked={selectedProducts.length === products.length && products.length > 0}
|
||||||
|
onChange={handleSelectAll}
|
||||||
|
/>
|
||||||
|
</th>
|
||||||
|
<th className='product' onClick={() => handleSort('name')}>
|
||||||
|
Product {renderSortIcon('name')}
|
||||||
|
</th>
|
||||||
|
<th className='commission text-center' onClick={() => handleSort('commission')}>
|
||||||
|
Commission Rate {renderSortIcon('commission')}
|
||||||
|
</th>
|
||||||
|
<th className='samples text-center' onClick={() => handleSort('availableSamples')}>
|
||||||
|
Available Samples {renderSortIcon('availableSamples')}
|
||||||
|
</th>
|
||||||
|
<th className='price text-center' onClick={() => handleSort('price')}>
|
||||||
|
Sales Price {renderSortIcon('price')}
|
||||||
|
</th>
|
||||||
|
<th className='stock text-center' onClick={() => handleSort('stock')}>
|
||||||
|
Stock {renderSortIcon('stock')}
|
||||||
|
</th>
|
||||||
|
<th className='sold text-center' onClick={() => handleSort('sold')}>
|
||||||
|
Items Sold {renderSortIcon('sold')}
|
||||||
|
</th>
|
||||||
|
<th className='rating text-center' onClick={() => handleSort('rating')}>
|
||||||
|
Product Rating {renderSortIcon('rating')}
|
||||||
|
</th>
|
||||||
|
<th className='creators text-center' onClick={() => handleSort('collabCreators')}>
|
||||||
|
Collab. Creators {renderSortIcon('collabCreators')}
|
||||||
|
</th>
|
||||||
|
<th className='tiktokShop text-center' onClick={() => handleSort('tiktokShop')}>
|
||||||
|
TikTok Shop {renderSortIcon('tiktokShop')}
|
||||||
|
</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{products.length === 0 ? (
|
||||||
|
<tr>
|
||||||
|
<td colSpan='10' className='text-center py-4'>
|
||||||
|
No products found for this brand.
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
) : (
|
||||||
|
products.map((product) => (
|
||||||
|
<tr key={product.id} className={selectedProducts.includes(product.id) ? 'selected' : ''}>
|
||||||
|
<td>
|
||||||
|
<Form.Check
|
||||||
|
type='checkbox'
|
||||||
|
checked={selectedProducts.includes(product.id)}
|
||||||
|
onChange={() => handleSelectProduct(product.id)}
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td className='product-cell'>
|
||||||
|
<div className='d-flex align-items-center'>
|
||||||
|
<div className='product-logo'>{product.name.slice(0, 1)}</div>
|
||||||
|
<div className='product-name'>{product.name}</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td className='text-center' >
|
||||||
|
<div>{product.commission}</div>
|
||||||
|
<div className='small text-muted'>Open collab. {product.openCollab}</div>
|
||||||
|
</td>
|
||||||
|
<td className='text-center'>{product.availableSamples}</td>
|
||||||
|
<td className='text-center'>{product.price}</td>
|
||||||
|
<td className='text-center'>{product.stock}</td>
|
||||||
|
<td className='text-center'>{product.sold}</td>
|
||||||
|
<td className='text-center'>
|
||||||
|
<div>{product.rating}</div>
|
||||||
|
<div className='small text-muted'>{product.reviews} Reviews</div>
|
||||||
|
</td>
|
||||||
|
<td className='text-center'>{product.collabCreators}</td>
|
||||||
|
<td className='text-center'>{product.tiktokShop && <FontAwesomeIcon icon='fa-brands fa-tiktok' />}</td>
|
||||||
|
</tr>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</tbody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -1,21 +1,39 @@
|
|||||||
import React, { useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import BrandsList from '../components/BrandsList';
|
import BrandsList from '../components/BrandsList';
|
||||||
import SearchBar from '../components/SearchBar';
|
import SearchBar from '../components/SearchBar';
|
||||||
import { Button, Modal, Form } from 'react-bootstrap';
|
import { Button, Modal, Form } from 'react-bootstrap';
|
||||||
import '../styles/Brands.scss';
|
import '../styles/Brands.scss';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { Plus } from 'lucide-react';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
import { selectBrand } from '../store/slices/brandsSlice';
|
||||||
|
|
||||||
export default function Brands() {
|
export default function Brands() {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const dispatch = useDispatch();
|
||||||
const [showAddBrandModal, setShowAddBrandModal] = useState(false);
|
const [showAddBrandModal, setShowAddBrandModal] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {}, [dispatch]);
|
||||||
|
|
||||||
|
const openBrandDetail = async (item) => {
|
||||||
|
console.log(item);
|
||||||
|
await dispatch(selectBrand(item));
|
||||||
|
navigate(`/brands/${item.id}`);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
<div className='function-bar'>
|
<div className='function-bar'>
|
||||||
<SearchBar />
|
<SearchBar />
|
||||||
<Button onClick={() => setShowAddBrandModal(true)}>+ Add Brand</Button>
|
<Button onClick={() => setShowAddBrandModal(true)}>
|
||||||
|
<Plus />
|
||||||
|
Add Brand
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<div className='breadcrumb'>
|
<div className='breadcrumb'>
|
||||||
<div className='breadcrumb-item'>Brands</div>
|
<div className='breadcrumb-item'>Brands</div>
|
||||||
</div>
|
</div>
|
||||||
<BrandsList />
|
<BrandsList openBrandDetail={openBrandDetail} />
|
||||||
<AddBrandModal show={showAddBrandModal} onHide={() => setShowAddBrandModal(false)} />
|
<AddBrandModal show={showAddBrandModal} onHide={() => setShowAddBrandModal(false)} />
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
@ -45,7 +63,7 @@ function AddBrandModal({ show, onHide }) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal show={show} onHide={onHide}>
|
<Modal show={show} onHide={onHide} backdropClassName='modal-backdrop' container={document.body}>
|
||||||
<Modal.Header>
|
<Modal.Header>
|
||||||
<Modal.Title>Add Brand</Modal.Title>
|
<Modal.Title>Add Brand</Modal.Title>
|
||||||
</Modal.Header>
|
</Modal.Header>
|
||||||
|
121
src/pages/BrandsDetail.jsx
Normal file
121
src/pages/BrandsDetail.jsx
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import SearchBar from '../components/SearchBar';
|
||||||
|
import { Button } from 'react-bootstrap';
|
||||||
|
import { Folders, Hash, LinkIcon, Plus, Users } from 'lucide-react';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import { Link, useParams } from 'react-router-dom';
|
||||||
|
import CampaignList from '../components/CampaignList';
|
||||||
|
import ProductsList from '../components/ProductsList';
|
||||||
|
import { findBrandById } from '../store/slices/brandsSlice';
|
||||||
|
|
||||||
|
export default function BrandsDetail() {
|
||||||
|
const { id } = useParams();
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const [activeTab, setActiveTab] = useState('campaigns');
|
||||||
|
const { selectedBrand } = useSelector((state) => state.brands);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (id) {
|
||||||
|
console.log(id);
|
||||||
|
|
||||||
|
dispatch(findBrandById(id));
|
||||||
|
}
|
||||||
|
}, [dispatch, id]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
selectedBrand?.id && (
|
||||||
|
<React.Fragment>
|
||||||
|
<div className='function-bar'>
|
||||||
|
<SearchBar />
|
||||||
|
{activeTab === 'campaigns' && (
|
||||||
|
<Button>
|
||||||
|
<Plus />
|
||||||
|
Add Campaign
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{activeTab === 'products' && (
|
||||||
|
<Button>
|
||||||
|
<Plus />
|
||||||
|
Add Product
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className='breadcrumb'>
|
||||||
|
<Link to={'/brands'} className='breadcrumb-item'>
|
||||||
|
Brands
|
||||||
|
</Link>
|
||||||
|
<div className='breadcrumb-item'>{selectedBrand.name}</div>
|
||||||
|
</div>
|
||||||
|
<div className='brand-detail-info'>
|
||||||
|
<div className='brand-logo shadow-xs'>{selectedBrand.name.toUpperCase()}</div>
|
||||||
|
<div className='brand-info shadow-xs'>
|
||||||
|
<div className='brand-info-top'>
|
||||||
|
<div className='info-item'>
|
||||||
|
<div className='info-name'>
|
||||||
|
<Folders size={20} />
|
||||||
|
商家分类
|
||||||
|
</div>
|
||||||
|
<div className='info-value'>{selectedBrand.category}</div>
|
||||||
|
</div>
|
||||||
|
<div className='info-item'>
|
||||||
|
<div className='info-name'>
|
||||||
|
<LinkIcon size={20} />
|
||||||
|
Source
|
||||||
|
</div>
|
||||||
|
<div className='info-value'>{selectedBrand.source}</div>
|
||||||
|
</div>
|
||||||
|
<div className='info-item'>
|
||||||
|
<div className='info-name'>
|
||||||
|
<Hash size={20} />
|
||||||
|
Collab.
|
||||||
|
</div>
|
||||||
|
<div className='info-value'>{selectedBrand.collab}</div>
|
||||||
|
</div>
|
||||||
|
<div className='info-item'>
|
||||||
|
<div className='info-name'>
|
||||||
|
<Users size={20} />
|
||||||
|
Creators
|
||||||
|
</div>
|
||||||
|
<div className='info-value'>{selectedBrand.creators}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='brand-info-bottom'>
|
||||||
|
<div className='info-item'>
|
||||||
|
<div className='info-value'>{selectedBrand.collab}</div>
|
||||||
|
<div className='info-name'>Total Collab. Creators</div>
|
||||||
|
</div>
|
||||||
|
<div className='info-item'>
|
||||||
|
<div className='info-value'>{selectedBrand.collab}</div>
|
||||||
|
<div className='info-name'>Total GMV Achieved</div>
|
||||||
|
</div>
|
||||||
|
<div className='info-item'>
|
||||||
|
<div className='info-value'>{selectedBrand.collab}</div>
|
||||||
|
<div className='info-name'>Total Views Achieved</div>
|
||||||
|
</div>
|
||||||
|
<div className='info-item'>
|
||||||
|
<div className='info-value'>{selectedBrand.collab}</div>
|
||||||
|
<div className='info-name'>Shop Overall Rating</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className='brand-tab-switches tab-switches'>
|
||||||
|
<div
|
||||||
|
className={`tab-switch-item ${activeTab === 'campaigns' ? 'active' : ''}`}
|
||||||
|
onClick={() => setActiveTab('campaigns')}
|
||||||
|
>
|
||||||
|
Campaigns
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`tab-switch-item ${activeTab === 'products' ? 'active' : ''}`}
|
||||||
|
onClick={() => setActiveTab('products')}
|
||||||
|
>
|
||||||
|
Products
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{activeTab === 'campaigns' && <CampaignList />}
|
||||||
|
{activeTab === 'products' && <ProductsList />}
|
||||||
|
</React.Fragment>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
109
src/pages/CampaignDetail.jsx
Normal file
109
src/pages/CampaignDetail.jsx
Normal file
@ -0,0 +1,109 @@
|
|||||||
|
import React, { useEffect, useState } from 'react';
|
||||||
|
import { Link, useParams } from 'react-router-dom';
|
||||||
|
import SearchBar from '../components/SearchBar';
|
||||||
|
import { Button, Form } from 'react-bootstrap';
|
||||||
|
import { useSelector, useDispatch } from 'react-redux';
|
||||||
|
import { fetchBrands, findCampaignById } from '../store/slices/brandsSlice';
|
||||||
|
import CampaignInfo from '../components/CampaignInfo';
|
||||||
|
import { ChevronRight, Send } from 'lucide-react';
|
||||||
|
import ProductsList from '../components/ProductsList';
|
||||||
|
export default function CampaignDetail() {
|
||||||
|
const { brandId, campaignId } = useParams();
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const { brands, selectedBrand, selectedCampaign } = useSelector((state) => state.brands);
|
||||||
|
const progressList = ['Find', 'Review', 'Confirmed', 'Draft Ready', 'Published'];
|
||||||
|
const [progressIndex, setProgressIndex] = useState(2);
|
||||||
|
const [activeTab, setActiveTab] = useState('products');
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
dispatch(fetchBrands());
|
||||||
|
}, [dispatch]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (brandId && campaignId) {
|
||||||
|
dispatch(findCampaignById({ brandId, campaignId }));
|
||||||
|
}
|
||||||
|
}, [brandId, campaignId]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
selectedCampaign?.id && (
|
||||||
|
<div className='campaign-detail'>
|
||||||
|
<div className='function-bar'>
|
||||||
|
<SearchBar />
|
||||||
|
<Button>
|
||||||
|
<Send size={18} />
|
||||||
|
Email
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
<div className='breadcrumb'>
|
||||||
|
<Link to={'/brands'} className='breadcrumb-item'>
|
||||||
|
Brands
|
||||||
|
</Link>
|
||||||
|
<Link to={`/brands/${brandId}`} className='breadcrumb-item'>{selectedBrand.name}</Link>
|
||||||
|
<div className='breadcrumb-item'>{selectedCampaign.name}</div>
|
||||||
|
</div>
|
||||||
|
<CampaignInfo />
|
||||||
|
<Form className='campaign-requirements shadow-xs'>
|
||||||
|
<Form.Group className='mb-3 additional_requirements' controlId='additional_requirements'>
|
||||||
|
<Form.Label>Additional Requirements</Form.Label>
|
||||||
|
<Form.Control type='text' placeholder='xxx' />
|
||||||
|
</Form.Group>
|
||||||
|
<Form.Group className='mb-3 creator_requirements' controlId='creator_requirements'>
|
||||||
|
<Form.Label>Creators</Form.Label>
|
||||||
|
<Form.Control type='number' />
|
||||||
|
</Form.Group>
|
||||||
|
<Button type='submit' variant='primary-subtle'>
|
||||||
|
Match Creators
|
||||||
|
</Button>
|
||||||
|
</Form>
|
||||||
|
<div className='campaign-progress shadow-xs'>
|
||||||
|
{progressList.map((item, index) =>
|
||||||
|
index < progressList.length - 1 ? (
|
||||||
|
<>
|
||||||
|
<div className={`campaign-progress-item ${progressIndex === index ? 'active' : ''}`} key={index}>
|
||||||
|
<div className='campaign-progress-item-index'>{index + 1}</div>
|
||||||
|
<div className='campaign-progress-item-label'>{item}</div>
|
||||||
|
<div className='campaign-progress-item-desc'>xx Creators</div>
|
||||||
|
</div>
|
||||||
|
<ChevronRight />
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<div className={`campaign-progress-item ${progressIndex === index ? 'active' : ''}`} key={index}>
|
||||||
|
<div className='campaign-progress-item-index'>{index + 1}</div>
|
||||||
|
<div className='campaign-progress-item-label'>{item}</div>
|
||||||
|
<div className='campaign-progress-item-desc'>xx Creators</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className='campaign-tab-switches tab-switches'>
|
||||||
|
<div
|
||||||
|
className={`tab-switch-item ${activeTab === 'products' ? 'active' : ''}`}
|
||||||
|
onClick={() => setActiveTab('products')}
|
||||||
|
>
|
||||||
|
Products
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`tab-switch-item ${activeTab === 'accepted_creators' ? 'active' : ''}`}
|
||||||
|
onClick={() => setActiveTab('accepted_creators')}
|
||||||
|
>
|
||||||
|
Accepted Creators
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`tab-switch-item ${activeTab === 'matching_result' ? 'active' : ''}`}
|
||||||
|
onClick={() => setActiveTab('matching_result')}
|
||||||
|
>
|
||||||
|
Matching Result
|
||||||
|
</div>
|
||||||
|
<div
|
||||||
|
className={`tab-switch-item ${activeTab === 'email_draft' ? 'active' : ''}`}
|
||||||
|
onClick={() => setActiveTab('email_draft')}
|
||||||
|
>
|
||||||
|
Email Draft
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{activeTab === 'products' && <ProductsList />}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
@ -3,8 +3,10 @@ import Home from '../pages/Home';
|
|||||||
import Database from '../pages/Database';
|
import Database from '../pages/Database';
|
||||||
import MainLayout from '../components/Layouts/MainLayout';
|
import MainLayout from '../components/Layouts/MainLayout';
|
||||||
import Brands from '../pages/Brands';
|
import Brands from '../pages/Brands';
|
||||||
import CreatorInbox from '../pages/CreatorInbox';
|
import CreatorInbox from '@/pages/CreatorInbox';
|
||||||
import DividLayout from '../components/Layouts/DividLayout';
|
import DividLayout from '@/components/Layouts/DividLayout';
|
||||||
|
import BrandsDetail from '@/pages/BrandsDetail';
|
||||||
|
import CampaignDetail from '../pages/CampaignDetail';
|
||||||
|
|
||||||
// Routes configuration object
|
// Routes configuration object
|
||||||
const routes = [
|
const routes = [
|
||||||
@ -49,7 +51,14 @@ const routes = [
|
|||||||
path: '/brands',
|
path: '/brands',
|
||||||
element: <Brands />,
|
element: <Brands />,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/brands/:id',
|
||||||
|
element: <BrandsDetail />,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: '/brands/:brandId/campaigns/:campaignId',
|
||||||
|
element: <CampaignDetail />,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/settings',
|
path: '/settings',
|
||||||
element: <Home />,
|
element: <Home />,
|
||||||
|
@ -1,4 +1,78 @@
|
|||||||
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
|
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
|
||||||
|
|
||||||
|
const mockCampaigns = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'SUNLINK拍拍灯',
|
||||||
|
service: '达人短视频-付费',
|
||||||
|
creators: 10,
|
||||||
|
creator_type: '带货类达人',
|
||||||
|
creator_level: ['L2', 'L3'],
|
||||||
|
category: ['家居', '生活', '数码'],
|
||||||
|
gmv: '$4k - $10k',
|
||||||
|
followers: 10000,
|
||||||
|
views: '500 - 10.5k',
|
||||||
|
budget: '$10 - $150',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: 'MINISO',
|
||||||
|
service: '达人短视频-付费',
|
||||||
|
creators: 10,
|
||||||
|
creator_type: '带货类达人',
|
||||||
|
creator_level: ['L2', 'L3'],
|
||||||
|
category: ['家居', '生活', '数码'],
|
||||||
|
gmv: '$4k - $10k',
|
||||||
|
followers: 10000,
|
||||||
|
views: '500 - 10.5k',
|
||||||
|
budget: '$10 - $150',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const mockProducts = [
|
||||||
|
{
|
||||||
|
id: 1,
|
||||||
|
name: 'Name',
|
||||||
|
commission: '15%',
|
||||||
|
openCollab: '12%',
|
||||||
|
availableSamples: 10,
|
||||||
|
price: '$78.90 - $118.90',
|
||||||
|
stock: 884,
|
||||||
|
sold: 732,
|
||||||
|
rating: 4.2,
|
||||||
|
reviews: 58,
|
||||||
|
collabCreators: 40,
|
||||||
|
tiktokShop: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 2,
|
||||||
|
name: 'Name',
|
||||||
|
commission: '15%',
|
||||||
|
openCollab: '12%',
|
||||||
|
availableSamples: 10,
|
||||||
|
price: '$78.90 - $118.90',
|
||||||
|
stock: 884,
|
||||||
|
sold: 732,
|
||||||
|
rating: 4.2,
|
||||||
|
reviews: 58,
|
||||||
|
collabCreators: 40,
|
||||||
|
tiktokShop: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 3,
|
||||||
|
name: 'Name',
|
||||||
|
commission: '15%',
|
||||||
|
openCollab: '12%',
|
||||||
|
availableSamples: 10,
|
||||||
|
price: '$78.90 - $118.90',
|
||||||
|
stock: 884,
|
||||||
|
sold: 732,
|
||||||
|
rating: 4.2,
|
||||||
|
reviews: 58,
|
||||||
|
collabCreators: 40,
|
||||||
|
tiktokShop: true,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
const mockBrands = [
|
const mockBrands = [
|
||||||
{
|
{
|
||||||
@ -10,6 +84,8 @@ const mockBrands = [
|
|||||||
source: 'TKS Official',
|
source: 'TKS Official',
|
||||||
description: 'Description 1',
|
description: 'Description 1',
|
||||||
website: 'https://www.brand1.com',
|
website: 'https://www.brand1.com',
|
||||||
|
campaigns: mockCampaigns,
|
||||||
|
products: mockProducts,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: 2,
|
id: 2,
|
||||||
@ -20,12 +96,15 @@ const mockBrands = [
|
|||||||
source: 'TKS Official',
|
source: 'TKS Official',
|
||||||
description: 'Description 1',
|
description: 'Description 1',
|
||||||
website: 'https://www.brand1.com',
|
website: 'https://www.brand1.com',
|
||||||
|
campaigns: mockCampaigns,
|
||||||
|
products: mockProducts,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const fetchBrands = createAsyncThunk('brands/fetchBrands', async () => {
|
export const fetchBrands = createAsyncThunk('brands/fetchBrands', async () => {
|
||||||
// const response = await fetch('https://api.example.com/brands');
|
// const response = await fetch('https://api.example.com/brands');
|
||||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||||
|
console.log('fetchBrands');
|
||||||
|
|
||||||
return mockBrands;
|
return mockBrands;
|
||||||
});
|
});
|
||||||
@ -35,6 +114,7 @@ const initialState = {
|
|||||||
status: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed'
|
status: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed'
|
||||||
error: null,
|
error: null,
|
||||||
selectedBrand: {},
|
selectedBrand: {},
|
||||||
|
selectedCampaign: {},
|
||||||
};
|
};
|
||||||
|
|
||||||
const brandsSlice = createSlice({
|
const brandsSlice = createSlice({
|
||||||
@ -44,6 +124,18 @@ const brandsSlice = createSlice({
|
|||||||
selectBrand: (state, action) => {
|
selectBrand: (state, action) => {
|
||||||
state.selectedBrand = action.payload;
|
state.selectedBrand = action.payload;
|
||||||
},
|
},
|
||||||
|
findBrandById: (state, action) => {
|
||||||
|
state.selectedBrand = state.brands.find((brand) => brand.id.toString() === action.payload);
|
||||||
|
},
|
||||||
|
findCampaignById: (state, action) => {
|
||||||
|
const { brandId, campaignId } = action.payload;
|
||||||
|
console.log(brandId, campaignId);
|
||||||
|
console.log(state.brands);
|
||||||
|
const brand = state.brands?.find((b) => b.id.toString() === brandId);
|
||||||
|
|
||||||
|
state.selectedBrand = brand;
|
||||||
|
state.selectedCampaign = brand?.campaigns?.find((c) => c.id.toString() === campaignId) || {};
|
||||||
|
},
|
||||||
},
|
},
|
||||||
extraReducers: (builder) => {
|
extraReducers: (builder) => {
|
||||||
builder
|
builder
|
||||||
@ -61,6 +153,6 @@ const brandsSlice = createSlice({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const { selectBrand } = brandsSlice.actions;
|
export const { selectBrand, findBrandById, findCampaignById } = brandsSlice.actions;
|
||||||
|
|
||||||
export default brandsSlice.reducer;
|
export default brandsSlice.reducer;
|
||||||
|
@ -58,3 +58,324 @@
|
|||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.brand-detail-info {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row wrap;
|
||||||
|
gap: 1rem;
|
||||||
|
|
||||||
|
.brand-logo {
|
||||||
|
height: 12.5rem;
|
||||||
|
max-width: 25rem;
|
||||||
|
flex-grow: 1;
|
||||||
|
background-color: #fff;
|
||||||
|
color: $primary;
|
||||||
|
font-size: 2rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-weight: 800;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
}
|
||||||
|
.brand-info {
|
||||||
|
flex-grow: 1;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
|
||||||
|
.brand-info-top {
|
||||||
|
padding: 1.5rem;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row nowrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
border-bottom: 1px solid $neutral-200;
|
||||||
|
|
||||||
|
.info-item {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row nowrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.375rem;
|
||||||
|
|
||||||
|
.info-name {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.brand-info-bottom {
|
||||||
|
padding: 1.5rem;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row nowrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
|
||||||
|
.info-item {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column nowrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
background-color: $neutral-150;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
padding: 1rem 0;
|
||||||
|
flex: 1;
|
||||||
|
|
||||||
|
.info-value {
|
||||||
|
font-weight: 800;
|
||||||
|
}
|
||||||
|
.info-name {
|
||||||
|
font-weight: 600;
|
||||||
|
color: $neutral-600;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tab-switches {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
margin: 1rem 0;
|
||||||
|
flex-flow: row nowrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
|
||||||
|
.tab-switch-item {
|
||||||
|
flex: 1;
|
||||||
|
text-align: center;
|
||||||
|
padding: 0.5rem 0;
|
||||||
|
background-color: $neutral-200;
|
||||||
|
transition: 0.25s;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
border-top-left-radius: 0.375rem;
|
||||||
|
border-bottom-left-radius: 0.375rem;
|
||||||
|
}
|
||||||
|
&:last-child {
|
||||||
|
border-top-right-radius: 0.375rem;
|
||||||
|
border-bottom-right-radius: 0.375rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background-color: $primary;
|
||||||
|
color: white;
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.8;
|
||||||
|
color: white;
|
||||||
|
background-color: $primary;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
background-color: $neutral-350;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.campaigns-list {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.875rem;
|
||||||
|
|
||||||
|
.campaign-info {
|
||||||
|
background-color: #fff;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||||||
|
padding: 1rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
border: 2px solid transparent;
|
||||||
|
transition: 0.25s;
|
||||||
|
width: 350px;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column nowrap;
|
||||||
|
gap: 0.5rem;
|
||||||
|
// &:hover {
|
||||||
|
// border: 2px solid $violet-400;
|
||||||
|
// }
|
||||||
|
|
||||||
|
.campaign-title {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 800;
|
||||||
|
margin-bottom: .5rem;
|
||||||
|
color: $primary;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.campaign-item {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.campaign-item-label {
|
||||||
|
width: 120px;
|
||||||
|
color: #666;
|
||||||
|
font-size: 14px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.campaign-item-value {
|
||||||
|
flex: 1;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.creator-level-tag {
|
||||||
|
background-color: #e8f0fe;
|
||||||
|
color: #1967d2;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.category-tag {
|
||||||
|
color: $primary;
|
||||||
|
padding: 2px 8px;
|
||||||
|
border-radius: 16px;
|
||||||
|
font-size: 12px;
|
||||||
|
border: 1px solid $primary;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.campaign-detail {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row wrap;
|
||||||
|
gap: 1rem;
|
||||||
|
|
||||||
|
.campaign-detail-info {
|
||||||
|
width: 100%;
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 1.5rem;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
|
||||||
|
.campaign-info-top {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column nowrap;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 0.25rem;
|
||||||
|
|
||||||
|
.campaign-name {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
font-weight: 800;
|
||||||
|
color: $primary;
|
||||||
|
}
|
||||||
|
.campaign-descp {
|
||||||
|
|
||||||
|
}
|
||||||
|
.campaign-edit {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
color: $primary;
|
||||||
|
cursor: pointer;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.campaign-info-bottom {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row wrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.campaign-info-item {
|
||||||
|
width: 30%;
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row nowrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
|
||||||
|
.campaign-info-item-label {
|
||||||
|
color: $neutral-700;
|
||||||
|
width: 8.5rem;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.campaign-requirements {
|
||||||
|
width: 30%;
|
||||||
|
max-width: 380px;
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 1rem 1.5rem;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row wrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
.additional_requirements {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
.creator_requirements {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row nowrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
width: 50%;
|
||||||
|
.form-label {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.campaign-progress {
|
||||||
|
flex: 1;
|
||||||
|
background-color: #fff;
|
||||||
|
padding: 1rem 1.5rem;
|
||||||
|
border-radius: 0.375rem;
|
||||||
|
display: flex;
|
||||||
|
flex-flow: row nowrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
.campaign-progress-item {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: column nowrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.375rem;
|
||||||
|
flex: 1;
|
||||||
|
border-bottom: 4px solid transparent;
|
||||||
|
padding: .375rem 0;
|
||||||
|
|
||||||
|
.campaign-progress-item-index {
|
||||||
|
width: 1.75rem;
|
||||||
|
height: 1.75rem;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1.75rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 1px solid $neutral-600;
|
||||||
|
color: $neutral-700;
|
||||||
|
}
|
||||||
|
.campaign-progress-item-desc {
|
||||||
|
font-size: 0.75rem;
|
||||||
|
color: $neutral-600;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
border-color: $primary;
|
||||||
|
.campaign-progress-item-index {
|
||||||
|
border-color: $primary;
|
||||||
|
background-color: $primary;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,186 +1,131 @@
|
|||||||
@import './custom-theme.scss';
|
@import './custom-theme.scss';
|
||||||
|
|
||||||
.creator-database-table {
|
.creator-database-table {
|
||||||
.table {
|
.creator-cell {
|
||||||
border-collapse: separate;
|
.creator-avatar {
|
||||||
border-spacing: 0;
|
position: relative;
|
||||||
|
width: 36px;
|
||||||
th {
|
height: 36px;
|
||||||
background-color: #f8f9fa;
|
margin-right: 12px;
|
||||||
border-top: none;
|
|
||||||
border-bottom: 1px solid #dee2e6;
|
img {
|
||||||
padding: 1rem 0.75rem;
|
width: 100%;
|
||||||
font-weight: 600;
|
height: 100%;
|
||||||
font-size: 0.875rem;
|
border-radius: 50%;
|
||||||
color: #495057;
|
object-fit: cover;
|
||||||
cursor: pointer;
|
border: 2px solid #e2e8f0;
|
||||||
position: relative;
|
}
|
||||||
vertical-align: middle;
|
|
||||||
|
.verified-badge {
|
||||||
&:first-child {
|
position: absolute;
|
||||||
border-top-left-radius: 0.5rem;
|
bottom: 0;
|
||||||
}
|
right: 0;
|
||||||
|
background-color: #10b981;
|
||||||
&:last-child {
|
color: white;
|
||||||
border-top-right-radius: 0.5rem;
|
border-radius: 50%;
|
||||||
}
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
&:hover {
|
font-size: 8px;
|
||||||
background-color: #f1f3f5;
|
display: flex;
|
||||||
}
|
align-items: center;
|
||||||
}
|
justify-content: center;
|
||||||
|
}
|
||||||
td {
|
}
|
||||||
padding: 0.75rem;
|
|
||||||
vertical-align: middle;
|
.creator-name {
|
||||||
border-bottom: 1px solid #f1f3f4;
|
font-weight: 500;
|
||||||
font-size: 0.875rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
tbody tr {
|
|
||||||
&:hover {
|
|
||||||
background-color: rgba(0, 0, 0, 0.02);
|
|
||||||
}
|
|
||||||
|
|
||||||
&.selected {
|
|
||||||
background-color: rgba(99, 102, 241, 0.05);
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: rgba(99, 102, 241, 0.1);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
&:last-child td {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
.category-pill {
|
||||||
.creator-cell {
|
display: inline-block;
|
||||||
.creator-avatar {
|
padding: 0.25rem 0.75rem;
|
||||||
position: relative;
|
border-radius: 1rem;
|
||||||
width: 36px;
|
background-color: #f8f9fa;
|
||||||
height: 36px;
|
font-size: 0.75rem;
|
||||||
margin-right: 12px;
|
color: #495057;
|
||||||
|
white-space: nowrap;
|
||||||
img {
|
|
||||||
width: 100%;
|
&.phones {
|
||||||
height: 100%;
|
background-color: rgba(99, 102, 241, 0.1);
|
||||||
|
color: #6366f1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.women {
|
||||||
|
background-color: rgba(244, 114, 182, 0.1);
|
||||||
|
color: #f472b6;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.sports {
|
||||||
|
background-color: rgba(52, 211, 153, 0.1);
|
||||||
|
color: #34d399;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.food {
|
||||||
|
background-color: rgba(251, 146, 60, 0.1);
|
||||||
|
color: #fb923c;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.health {
|
||||||
|
background-color: rgba(56, 189, 248, 0.1);
|
||||||
|
color: #38bdf8;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.kitchen {
|
||||||
|
background-color: rgba(168, 85, 247, 0.1);
|
||||||
|
color: #a855f7;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.level-badge {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
border-radius: 1rem;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
white-space: nowrap;
|
||||||
|
|
||||||
|
&.ecommerce-level {
|
||||||
|
background-color: rgba(99, 102, 241, 0.1);
|
||||||
|
color: #6366f1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.exposure-level {
|
||||||
|
background-color: rgba(14, 165, 233, 0.1);
|
||||||
|
color: #0ea5e9;
|
||||||
|
|
||||||
|
&[data-level^='KOC'] {
|
||||||
|
background-color: rgba(52, 211, 153, 0.1);
|
||||||
|
color: #34d399;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-level^='KOL'] {
|
||||||
|
background-color: rgba(251, 113, 133, 0.1);
|
||||||
|
color: #fb7185;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.colored-dot {
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
object-fit: cover;
|
display: inline-block;
|
||||||
border: 2px solid #e2e8f0;
|
|
||||||
}
|
&.blue {
|
||||||
|
background-color: #6366f1;
|
||||||
.verified-badge {
|
}
|
||||||
position: absolute;
|
}
|
||||||
bottom: 0;
|
|
||||||
right: 0;
|
.social-icon {
|
||||||
background-color: #10b981;
|
width: 24px;
|
||||||
color: white;
|
height: 24px;
|
||||||
border-radius: 50%;
|
|
||||||
width: 10px;
|
|
||||||
height: 10px;
|
|
||||||
font-size: 8px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
|
||||||
|
&.tiktok-icon {
|
||||||
|
font-size: 14px;
|
||||||
|
color: #000000;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
.creator-name {
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.category-pill {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 0.25rem 0.75rem;
|
|
||||||
border-radius: 1rem;
|
|
||||||
background-color: #f8f9fa;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
color: #495057;
|
|
||||||
white-space: nowrap;
|
|
||||||
|
|
||||||
&.phones {
|
|
||||||
background-color: rgba(99, 102, 241, 0.1);
|
|
||||||
color: #6366f1;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.women {
|
|
||||||
background-color: rgba(244, 114, 182, 0.1);
|
|
||||||
color: #f472b6;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.sports {
|
|
||||||
background-color: rgba(52, 211, 153, 0.1);
|
|
||||||
color: #34d399;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.food {
|
|
||||||
background-color: rgba(251, 146, 60, 0.1);
|
|
||||||
color: #fb923c;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.health {
|
|
||||||
background-color: rgba(56, 189, 248, 0.1);
|
|
||||||
color: #38bdf8;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.kitchen {
|
|
||||||
background-color: rgba(168, 85, 247, 0.1);
|
|
||||||
color: #a855f7;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.level-badge {
|
|
||||||
display: inline-block;
|
|
||||||
padding: 0.25rem 0.75rem;
|
|
||||||
border-radius: 1rem;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
white-space: nowrap;
|
|
||||||
|
|
||||||
&.ecommerce-level {
|
|
||||||
background-color: rgba(99, 102, 241, 0.1);
|
|
||||||
color: #6366f1;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.exposure-level {
|
|
||||||
background-color: rgba(14, 165, 233, 0.1);
|
|
||||||
color: #0ea5e9;
|
|
||||||
|
|
||||||
&[data-level^="KOC"] {
|
|
||||||
background-color: rgba(52, 211, 153, 0.1);
|
|
||||||
color: #34d399;
|
|
||||||
}
|
|
||||||
|
|
||||||
&[data-level^="KOL"] {
|
|
||||||
background-color: rgba(251, 113, 133, 0.1);
|
|
||||||
color: #fb7185;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.colored-dot {
|
|
||||||
width: 8px;
|
|
||||||
height: 8px;
|
|
||||||
border-radius: 50%;
|
|
||||||
display: inline-block;
|
|
||||||
|
|
||||||
&.blue {
|
|
||||||
background-color: #6366f1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.social-icon {
|
|
||||||
width: 24px;
|
|
||||||
height: 24px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
|
|
||||||
&.tiktok-icon {
|
|
||||||
font-size: 14px;
|
|
||||||
color: #000000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
16
src/styles/Products.scss
Normal file
16
src/styles/Products.scss
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
.products-list {
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
.product-logo {
|
||||||
|
position: relative;
|
||||||
|
width: 3rem;
|
||||||
|
height: 3rem;
|
||||||
|
line-height: 3rem;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 0.5rem;
|
||||||
|
background-color: #7f55e0;
|
||||||
|
color: white;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
margin-right: .25rem;
|
||||||
|
}
|
||||||
|
}
|
@ -15,10 +15,11 @@ $indigo-100: #e0e7ff;
|
|||||||
$indigo-500: #6366f1;
|
$indigo-500: #6366f1;
|
||||||
$violet-50: #f5f3ff;
|
$violet-50: #f5f3ff;
|
||||||
$violet-100: #ede9fe;
|
$violet-100: #ede9fe;
|
||||||
$violet-150: #E0E1FAFF;
|
$violet-150: #e0e1faff;
|
||||||
$neutral-150: #f8f9faFF;
|
$violet-400: #a78bfa;
|
||||||
$neutral-200: #F3F4F6FF;
|
$neutral-150: #f8f9faff;
|
||||||
$neutral-350: #CFD2DAFF;
|
$neutral-200: #f3f4f6ff;
|
||||||
|
$neutral-350: #cfd2daff;
|
||||||
$neutral-600: #565e6cff;
|
$neutral-600: #565e6cff;
|
||||||
$neutral-700: #323842ff;
|
$neutral-700: #323842ff;
|
||||||
$neutral-900: #171a1fff;
|
$neutral-900: #171a1fff;
|
||||||
@ -37,7 +38,7 @@ $box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
|
|||||||
|
|
||||||
:root {
|
:root {
|
||||||
--bs-breadcrumb-font-size: 1.5rem;
|
--bs-breadcrumb-font-size: 1.5rem;
|
||||||
--bs-body-color: #171A1FFF;
|
--bs-body-color: #171a1fff;
|
||||||
--bs-btn-color: white !important;
|
--bs-btn-color: white !important;
|
||||||
--bs-btn-hover-color: white !important;
|
--bs-btn-hover-color: white !important;
|
||||||
}
|
}
|
||||||
@ -51,7 +52,17 @@ $box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);
|
|||||||
--bs-btn-hover-color: white !important;
|
--bs-btn-hover-color: white !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn-primary-subtle {
|
||||||
|
background-color: $violet-150;
|
||||||
|
color: $indigo-500;
|
||||||
|
&:hover {
|
||||||
|
background-color: $primary !important;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#root {
|
#root {
|
||||||
|
font-weight: 500;
|
||||||
background-color: #f5f3ff;
|
background-color: #f5f3ff;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,3 +72,62 @@ a {
|
|||||||
text-decoration: none !important;
|
text-decoration: none !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.shadow-xs {
|
||||||
|
box-shadow: 0px 0px 1px #171a1f12, 0px 0px 2px #171a1f1f; /* shadow-xs */
|
||||||
|
}
|
||||||
|
|
||||||
|
.table {
|
||||||
|
border-collapse: separate;
|
||||||
|
border-spacing: 0;
|
||||||
|
|
||||||
|
th {
|
||||||
|
background-color: #f8f9fa;
|
||||||
|
border-top: none;
|
||||||
|
border-bottom: 1px solid #dee2e6;
|
||||||
|
padding: 1rem 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: #495057;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
vertical-align: middle;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
border-top-left-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-top-right-radius: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: #f1f3f5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
padding: 0.75rem;
|
||||||
|
vertical-align: middle;
|
||||||
|
border-bottom: 1px solid #f1f3f4;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody tr {
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba(0, 0, 0, 0.02);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.selected {
|
||||||
|
background-color: rgba(99, 102, 241, 0.05);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: rgba(99, 102, 241, 0.1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child td {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -10,7 +10,13 @@
|
|||||||
.function-bar {
|
.function-bar {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-flow: row wrap;
|
flex-flow: row wrap;
|
||||||
|
align-items: center;
|
||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
justify-content: flex-end;
|
justify-content: flex-end;
|
||||||
float: right;
|
float: right;
|
||||||
|
button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user