mirror of
https://github.com/Funkoala14/CreatorCenter_OOIN.git
synced 2025-06-08 02:58:14 +08:00
[dev]product detail's campaign&creators
This commit is contained in:
parent
0bd25c4f2a
commit
a259dc3403
@ -1,13 +1,13 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { Button, Form, Modal } from 'react-bootstrap';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { fetchCampaigns } from '../store/slices/brandsSlice';
|
||||
import { fetchCampaigns } from '../store/slices/campaignSlice';
|
||||
import SpinningComponent from './SpinningComponent';
|
||||
import { addCreatorsToCampaign } from '../store/slices/creatorsSlice';
|
||||
|
||||
export default function AddToCampaign({ show, onHide }) {
|
||||
const dispatch = useDispatch();
|
||||
const { campaigns, status } = useSelector((state) => state.brands);
|
||||
const { campaigns, status } = useSelector((state) => state.campaign);
|
||||
const { selectedCreators } = useSelector((state) => state.creators);
|
||||
const [campaignId, setCampaignId] = useState(null);
|
||||
const [validated, setValidated] = useState(false);
|
||||
|
@ -4,7 +4,7 @@ import { fetchBrands } from '../store/slices/brandsSlice';
|
||||
import { Card } from 'react-bootstrap';
|
||||
import { Folders, Hash, Link, Users } from 'lucide-react';
|
||||
import SpinningComponent from './SpinningComponent';
|
||||
import { getBrandSourceName } from '../lib/utils';
|
||||
import { getBrandSourceName } from '../lib/utils.jsx';
|
||||
|
||||
export default function BrandsList({ openBrandDetail }) {
|
||||
const { brands, status, error } = useSelector((state) => state.brands);
|
||||
|
@ -2,12 +2,12 @@ import { ChartNoAxesColumnIncreasing, CircleDollarSign, Edit, Eye, Folders, Hash
|
||||
import { useSelector } from 'react-redux';
|
||||
|
||||
export default function CampaignInfo() {
|
||||
const { selectedCampaign } = useSelector((state) => state.brands);
|
||||
const { currentCampaign } = useSelector((state) => state.campaign);
|
||||
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-name'>{currentCampaign.name}</div>
|
||||
<div className='campaign-descp'>{currentCampaign.description || '--'}</div>
|
||||
<div className='campaign-edit'>
|
||||
<Edit size={18} />
|
||||
Edit
|
||||
@ -19,7 +19,7 @@ export default function CampaignInfo() {
|
||||
<Layers size={18} />
|
||||
Service
|
||||
</div>
|
||||
<div className='campaign-info-item-value'>{selectedCampaign?.service || '--'}</div>
|
||||
<div className='campaign-info-item-value'>{currentCampaign?.service || '--'}</div>
|
||||
</div>
|
||||
<div className='campaign-info-item'>
|
||||
<div className='campaign-info-item-label'>
|
||||
@ -27,9 +27,9 @@ export default function CampaignInfo() {
|
||||
Category
|
||||
</div>
|
||||
<div className='campaign-info-item-value'>
|
||||
{selectedCampaign?.creator_category || '--'}
|
||||
{/* {selectedCampaign?.category?.length > 0 &&
|
||||
selectedCampaign.category.map((cat,index) => <span className='category-tag' key={index}>{cat}</span>)} */}
|
||||
{currentCampaign?.creator_category || '--'}
|
||||
{/* {currentCampaign?.category?.length > 0 &&
|
||||
currentCampaign.category.map((cat,index) => <span className='category-tag' key={index}>{cat}</span>)} */}
|
||||
</div>
|
||||
</div>
|
||||
<div className='campaign-info-item'>
|
||||
@ -37,28 +37,28 @@ export default function CampaignInfo() {
|
||||
<UserRoundCheck size={18} />
|
||||
Followers
|
||||
</div>
|
||||
<div className='campaign-info-item-value'>{selectedCampaign?.followers || '--'}</div>
|
||||
<div className='campaign-info-item-value'>{currentCampaign?.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 className='campaign-info-item-value'>{currentCampaign?.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 className='campaign-info-item-value'>{currentCampaign?.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?.budget || '--'}</div>
|
||||
<div className='campaign-info-item-value'>{currentCampaign?.budget || '--'}</div>
|
||||
</div>
|
||||
<div className='campaign-info-item'>
|
||||
<div className='campaign-info-item-label'>
|
||||
@ -66,9 +66,9 @@ export default function CampaignInfo() {
|
||||
Creator Level
|
||||
</div>
|
||||
<div className='campaign-info-item-value'>
|
||||
{selectedCampaign?.creator_level || '--'}
|
||||
{/* {selectedCampaign?.creator_level?.length > 0 &&
|
||||
selectedCampaign.creator_level.map((level,index) => (
|
||||
{currentCampaign?.creator_level || '--'}
|
||||
{/* {currentCampaign?.creator_level?.length > 0 &&
|
||||
currentCampaign.creator_level.map((level,index) => (
|
||||
<span className='creator-level-tag' key={index}>{level}</span>
|
||||
))} */}
|
||||
</div>
|
||||
@ -78,14 +78,14 @@ export default function CampaignInfo() {
|
||||
<Eye size={18} />
|
||||
Views
|
||||
</div>
|
||||
<div className='campaign-info-item-value'>{selectedCampaign?.views || '--'}</div>
|
||||
<div className='campaign-info-item-value'>{currentCampaign?.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_count || '--'}</div>
|
||||
<div className='campaign-info-item-value'>{currentCampaign?.creators_count || '--'}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -3,7 +3,7 @@ import { useState } from 'react';
|
||||
import { Button, Modal, Table } from 'react-bootstrap';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { getCategoryClassName } from '../lib/utils';
|
||||
import { getCategoryClassName } from '../lib/utils.jsx';
|
||||
|
||||
export default function DiscoveryList() {
|
||||
const dispatch = useDispatch();
|
||||
|
@ -2,7 +2,7 @@ import { useEffect, useState } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { fetchInboxList } from '../store/slices/inboxSlice';
|
||||
import { fetchChatDetails, selectChat } from '../store/slices/chatSlice';
|
||||
import { chatDateFormat } from '../lib/utils';
|
||||
import { chatDateFormat } from '../lib/utils.jsx';
|
||||
|
||||
export default function InboxList() {
|
||||
const dispatch = useDispatch();
|
||||
|
@ -1,62 +1,63 @@
|
||||
import { useEffect } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { fetchProductDetail } from '../store/slices/productSlice';
|
||||
import { fetchCampaignCreators, fetchProductCampaigns, fetchProductDetail } from '../store/slices/productSlice';
|
||||
import { mockCreators } from '../store/slices/creatorsSlice';
|
||||
import { Accordion, Table } from 'react-bootstrap';
|
||||
import { Accordion, Alert, Table } from 'react-bootstrap';
|
||||
import SpinningComponent from './SpinningComponent';
|
||||
|
||||
export default function ProductDetail() {
|
||||
const selectedProduct = useSelector((state) => state.brands.selectedProduct);
|
||||
const { currentProduct } = useSelector((state) => state.products);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchProductDetail(selectedProduct.id));
|
||||
}, [selectedProduct.id]);
|
||||
dispatch(fetchProductDetail(currentProduct.id));
|
||||
}, [currentProduct.id]);
|
||||
|
||||
return (
|
||||
<div className='product-details shadow-xs'>
|
||||
<div className='product-details-header'>
|
||||
<div className='product-details-header-title'>{selectedProduct.name}</div>
|
||||
<div className='product-details-header-pid'>PID: {selectedProduct.id}</div>
|
||||
<div className='product-details-header-title'>{currentProduct.name}</div>
|
||||
<div className='product-details-header-pid'>PID: {currentProduct.id}</div>
|
||||
</div>
|
||||
<div className='product-details-body'>
|
||||
<div className='product-img'></div>
|
||||
<div className='product-detail-list'>
|
||||
<div className='product-detail-item'>
|
||||
<div className='product-detail-item-value'>{selectedProduct.commission_rate}</div>
|
||||
<div className='product-detail-item-value'>{currentProduct.commission_rate}</div>
|
||||
<div className='product-detail-item-label'>Commission Rate</div>
|
||||
</div>
|
||||
<div className='product-detail-item'>
|
||||
<div className='product-detail-item-value'>{selectedProduct.available_samples}</div>
|
||||
<div className='product-detail-item-value'>{currentProduct.available_samples}</div>
|
||||
<div className='product-detail-item-label'>Available Samples</div>
|
||||
</div>
|
||||
<div className='product-detail-item'>
|
||||
<div className='product-detail-item-value'>
|
||||
{selectedProduct.sales_price_max} - {selectedProduct.sales_price_min}
|
||||
{currentProduct.sales_price_max} - {currentProduct.sales_price_min}
|
||||
</div>
|
||||
<div className='product-detail-item-label'>Sales Price</div>
|
||||
</div>
|
||||
<div className='product-detail-item'>
|
||||
<div className='product-detail-item-value'>{selectedProduct.stock}</div>
|
||||
<div className='product-detail-item-value'>{currentProduct.stock}</div>
|
||||
<div className='product-detail-item-label'>Stock</div>
|
||||
</div>
|
||||
<div className='product-detail-item'>
|
||||
<div className='product-detail-item-value'>{selectedProduct.items_sold}</div>
|
||||
<div className='product-detail-item-value'>{currentProduct.items_sold}</div>
|
||||
<div className='product-detail-item-label'>Items Sold</div>
|
||||
</div>
|
||||
<div className='product-detail-item'>
|
||||
<div className='product-detail-item-value'>{selectedProduct.product_rating}</div>
|
||||
<div className='product-detail-item-value'>{currentProduct.product_rating}</div>
|
||||
<div className='product-detail-item-label'>Product Rating</div>
|
||||
</div>
|
||||
<div className='product-detail-item'>
|
||||
<div className='product-detail-item-value'>{selectedProduct.collab_creators}</div>
|
||||
<div className='product-detail-item-value'>{currentProduct.collab_creators}</div>
|
||||
<div className='product-detail-item-label'>Collab Creators</div>
|
||||
</div>
|
||||
<div className='product-detail-item'>
|
||||
<div className='product-detail-item-value'>{selectedProduct.gmv}</div>
|
||||
<div className='product-detail-item-value'>{currentProduct.gmv}</div>
|
||||
<div className='product-detail-item-label'>GMV Achieved</div>
|
||||
</div>
|
||||
<div className='product-detail-item'>
|
||||
<div className='product-detail-item-value'>{selectedProduct.reviews_count}</div>
|
||||
<div className='product-detail-item-value'>{currentProduct.reviews_count}</div>
|
||||
<div className='product-detail-item-label'>Views Achieved</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -66,27 +67,26 @@ export default function ProductDetail() {
|
||||
}
|
||||
|
||||
export const CampaignsCollabCreators = () => {
|
||||
const mockData = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'SUNLINK 拍拍灯',
|
||||
creatorList: mockCreators,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'SUNLINK 拍拍灯2',
|
||||
creatorList: mockCreators,
|
||||
},
|
||||
];
|
||||
const { currentProduct, status } = useSelector((state) => state.products);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
if (mockData.length === 0) {
|
||||
return <div className='text-center'>No campaigns found</div>;
|
||||
useEffect(() => {
|
||||
const fetchCampaigns = async () => {
|
||||
await dispatch(fetchProductCampaigns(currentProduct.id)).unwrap();
|
||||
};
|
||||
fetchCampaigns();
|
||||
}, []);
|
||||
|
||||
if (status === 'loading') {
|
||||
return <SpinningComponent />;
|
||||
}
|
||||
if (!currentProduct.campaigns || currentProduct.campaigns.length < 0) {
|
||||
return <Alert variant='info'>No campaigns found</Alert>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Accordion className='campaigns-collab-creators-list' defaultActiveKey={mockData[0].id}>
|
||||
这个接口是用哪个?根据pid获取关联的活动-根据活动获取creatorList
|
||||
{mockData.map((item) => (
|
||||
<Accordion className='campaigns-collab-creators-list' defaultActiveKey={currentProduct.campaigns[0].id}>
|
||||
{currentProduct.campaigns.map((item) => (
|
||||
<Accordion.Item eventKey={item.id} key={item.id} className='campaigns-collab-creators-item'>
|
||||
<Accordion.Header>{item.name}</Accordion.Header>
|
||||
<Accordion.Body>
|
||||
@ -103,8 +103,8 @@ export const CampaignsCollabCreators = () => {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{item?.creatorList.length > 0 &&
|
||||
item?.creatorList.map((creator) => (
|
||||
{item?.creators?.length > 0 ? (
|
||||
item?.creators.map((creator) => (
|
||||
<tr key={creator.id}>
|
||||
<td>
|
||||
<div className='white-space-nowrap'>
|
||||
@ -123,7 +123,14 @@ export const CampaignsCollabCreators = () => {
|
||||
<td>{creator.pricing || '--'}</td>
|
||||
<td>{creator.status || '--'}</td>
|
||||
</tr>
|
||||
))}
|
||||
))
|
||||
) : (
|
||||
<tr>
|
||||
<td colSpan={7} className='text-center'>
|
||||
No creators found
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</Table>
|
||||
</Accordion.Body>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Spinner } from "react-bootstrap";
|
||||
import { Spinner } from 'react-bootstrap';
|
||||
|
||||
export default function SpinningComponent() {
|
||||
return (
|
||||
|
@ -1,5 +1,8 @@
|
||||
import { format, isToday, parseISO } from 'date-fns';
|
||||
import { BRAND_SOURCES } from "./constant";
|
||||
import { BRAND_SOURCES } from './constant';
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||
import { Link } from 'react-router-dom';
|
||||
import React from 'react';
|
||||
|
||||
/**
|
||||
* 格式化日期
|
||||
@ -56,11 +59,11 @@ export const getCategoryClassName = (category) => {
|
||||
};
|
||||
|
||||
export const getBrandSourceName = (source) => {
|
||||
return BRAND_SOURCES.find(item => item.value === source)?.name || source;
|
||||
return BRAND_SOURCES.find((item) => item.value === source)?.name || source;
|
||||
};
|
||||
|
||||
export const getTemplateMissionName = (mission) => {
|
||||
return TEMPLATE_MISSIONS.find(item => item.value === mission)?.name || mission;
|
||||
return TEMPLATE_MISSIONS.find((item) => item.value === mission)?.name || mission;
|
||||
};
|
||||
|
||||
export const chatDateFormat = (date) => {
|
||||
@ -71,3 +74,23 @@ export const chatDateFormat = (date) => {
|
||||
return format(now, 'MMM do');
|
||||
}
|
||||
};
|
||||
|
||||
export function determinProfileIcon(creator) {
|
||||
if (!creator) return '--';
|
||||
console.log(creator);
|
||||
if (creator.profile === 'tiktok') {
|
||||
return <Link to={creator.tiktok_link || ''} target='_blank'>
|
||||
<FontAwesomeIcon icon='fa-brands fa-tiktok' />
|
||||
</Link>;
|
||||
} else if (creator.profile === 'youtube') {
|
||||
return <Link to={creator.youtube_link || ''} target='_blank'>
|
||||
<FontAwesomeIcon icon='fa-brands fa-youtube' />
|
||||
</Link>;
|
||||
} else if (creator.profile === 'instagram') {
|
||||
return <Link to={creator.instagram_link || ''} target='_blank'>
|
||||
<FontAwesomeIcon icon='fa-brands fa-instagram' />
|
||||
</Link>;
|
||||
} else {
|
||||
return '--';
|
||||
}
|
||||
}
|
@ -10,9 +10,9 @@ import {
|
||||
fetchBrandDetail,
|
||||
fetchBrandCampaigns,
|
||||
fetchBrandProducts,
|
||||
setSelectedProduct,
|
||||
createCampaignThunk,
|
||||
} from '../store/slices/brandsSlice';
|
||||
import { setCurrentProduct } from '../store/slices/productSlice';
|
||||
import SlidePanel from '../components/SlidePanel';
|
||||
import ProductDetail, { CampaignsCollabCreators } from '../components/ProductDetail';
|
||||
import { CAMPAIGN_SERVICES, CREATOR_CATEGORIES, CREATOR_LEVELS, CREATOR_TYPES, GMV_RANGES } from '../lib/constant';
|
||||
@ -30,14 +30,18 @@ export default function BrandsDetail() {
|
||||
|
||||
useEffect(() => {
|
||||
if (id) {
|
||||
dispatch(fetchBrandDetail(id));
|
||||
const fetchData = async () => {
|
||||
await dispatch(fetchBrandDetail(id)).unwrap();
|
||||
dispatch(fetchBrandCampaigns(id));
|
||||
dispatch(fetchBrandProducts(id));
|
||||
};
|
||||
|
||||
fetchData();
|
||||
}
|
||||
}, [dispatch, id]);
|
||||
|
||||
const handleShowProductDetail = (product) => {
|
||||
dispatch(setSelectedProduct(product));
|
||||
dispatch(setCurrentProduct(product));
|
||||
setShowProductDetail(true);
|
||||
};
|
||||
|
||||
@ -158,11 +162,7 @@ export default function BrandsDetail() {
|
||||
onHide={() => setShowAddCampaignModal(false)}
|
||||
brandId={id}
|
||||
/>
|
||||
<AddProductModal
|
||||
show={showAddProductModal}
|
||||
onHide={() => setShowAddProductModal(false)}
|
||||
brandId={id}
|
||||
/>
|
||||
<AddProductModal show={showAddProductModal} onHide={() => setShowAddProductModal(false)} brandId={id} />
|
||||
</React.Fragment>
|
||||
)
|
||||
);
|
||||
|
@ -3,52 +3,75 @@ import { Link, useParams } from 'react-router-dom';
|
||||
import SearchBar from '../components/SearchBar';
|
||||
import { Button, Col, Form, Modal, Row, Spinner, Table } from 'react-bootstrap';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { fetchBrands, fetchBrandDetail, fetchCampaignDetail, setSelectedProduct } from '../store/slices/brandsSlice';
|
||||
import { fetchBrandDetail } from '../store/slices/brandsSlice';
|
||||
import { fetchCampaignDetail, fetchMatchingResult, resetCurrentCampaign } from '../store/slices/campaignSlice';
|
||||
import CampaignInfo from '../components/CampaignInfo';
|
||||
import { ChevronRight, Send, Plus } from 'lucide-react';
|
||||
import ProductsList from '../components/ProductsList';
|
||||
import SlidePanel from '../components/SlidePanel';
|
||||
import CampaignScript from './CampaignScript';
|
||||
import { addProductToCampaign, fetchProducts } from '../store/slices/productSlice';
|
||||
import { addProductToCampaign, fetchProducts, setCurrentProduct } from '../store/slices/productSlice';
|
||||
import { determinProfileIcon } from '../lib/utils.jsx';
|
||||
|
||||
export default function CampaignDetail() {
|
||||
const { brandId, campaignId } = useParams();
|
||||
const dispatch = useDispatch();
|
||||
const { brands, selectedBrand, selectedCampaign } = useSelector((state) => state.brands);
|
||||
const { selectedBrand } = useSelector((state) => state.brands);
|
||||
const { currentCampaign } = useSelector((state) => state.campaign);
|
||||
const progressList = ['Find', 'Review', 'Confirmed', 'Draft Ready', 'Published'];
|
||||
const [progressIndex, setProgressIndex] = useState(2);
|
||||
const [activeTab, setActiveTab] = useState('products');
|
||||
const [showProductDetail, setShowProductDetail] = useState(false);
|
||||
const [showAddProductModal, setShowAddProductModal] = useState(false);
|
||||
const [additionalRequirements, setAdditionalRequirements] = useState({
|
||||
criteria: 'default',
|
||||
top_n: 10,
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (brandId && campaignId) {
|
||||
const fetchData = async () => {
|
||||
console.log(selectedBrand);
|
||||
if (!selectedBrand?.id) {
|
||||
dispatch(fetchBrandDetail(brandId));
|
||||
await dispatch(fetchBrandDetail(brandId)).unwrap();
|
||||
dispatch(fetchCampaignDetail(campaignId));
|
||||
} else {
|
||||
dispatch(fetchCampaignDetail(campaignId));
|
||||
}
|
||||
dispatch(fetchMatchingResult(additionalRequirements));
|
||||
};
|
||||
fetchData();
|
||||
}
|
||||
dispatch(fetchProducts());
|
||||
}, [dispatch, brandId, campaignId]);
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
dispatch(resetCurrentCampaign());
|
||||
};
|
||||
}, []);
|
||||
|
||||
const handleShowProductDetail = (product) => {
|
||||
dispatch(setSelectedProduct(product));
|
||||
dispatch(setCurrentProduct(product));
|
||||
setShowProductDetail(true);
|
||||
};
|
||||
|
||||
const handleMatchCreators = async (e) => {
|
||||
e.preventDefault();
|
||||
await dispatch(fetchMatchingResult(additionalRequirements)).unwrap();
|
||||
};
|
||||
|
||||
return (
|
||||
selectedCampaign?.id && (
|
||||
currentCampaign && (
|
||||
<div className='campaign-detail'>
|
||||
<div className='breadcrumb'>
|
||||
<Link to={'/brands'} className='breadcrumb-item'>
|
||||
Brands
|
||||
</Link>
|
||||
<Link to={`/brands/${brandId}`} className='breadcrumb-item'>
|
||||
{selectedBrand.name}
|
||||
{selectedBrand?.name}
|
||||
</Link>
|
||||
<div className='breadcrumb-item'>{selectedCampaign.name}</div>
|
||||
<div className='breadcrumb-item'>{currentCampaign.name}</div>
|
||||
</div>
|
||||
<div className='function-bar'>
|
||||
<SearchBar />
|
||||
@ -58,14 +81,27 @@ export default function CampaignDetail() {
|
||||
</Button>
|
||||
</div>
|
||||
<CampaignInfo />
|
||||
<Form className='campaign-requirements shadow-xs'>
|
||||
<Form className='campaign-requirements shadow-xs' onSubmit={handleMatchCreators}>
|
||||
<Form.Group className='mb-3 additional_requirements' controlId='additional_requirements'>
|
||||
<Form.Label>Additional Requirements</Form.Label>
|
||||
<Form.Control type='text' placeholder='xxx' />
|
||||
<Form.Control
|
||||
type='text'
|
||||
placeholder=''
|
||||
value={additionalRequirements.criteria}
|
||||
onChange={(e) =>
|
||||
setAdditionalRequirements({ ...additionalRequirements, criteria: e.target.value })
|
||||
}
|
||||
/>
|
||||
</Form.Group>
|
||||
<Form.Group className='mb-3 creator_requirements' controlId='creator_requirements'>
|
||||
<Form.Label>Creators</Form.Label>
|
||||
<Form.Control type='number' />
|
||||
<Form.Control
|
||||
type='number'
|
||||
value={additionalRequirements.top_n}
|
||||
onChange={(e) =>
|
||||
setAdditionalRequirements({ ...additionalRequirements, top_n: e.target.value })
|
||||
}
|
||||
/>
|
||||
</Form.Group>
|
||||
<Button type='submit' variant='primary-subtle'>
|
||||
Match Creators
|
||||
@ -135,7 +171,7 @@ export default function CampaignDetail() {
|
||||
Add Product
|
||||
</Button>
|
||||
<ProductsList
|
||||
products={selectedCampaign?.link_product_details}
|
||||
products={currentCampaign?.link_product_details}
|
||||
onShowProductDetail={handleShowProductDetail}
|
||||
/>
|
||||
<SlidePanel
|
||||
@ -216,7 +252,7 @@ function AddProductModal({ campaignId, show, onHide }) {
|
||||
}
|
||||
|
||||
function AcceptedCreators() {
|
||||
const { selectedCampaign } = useSelector((state) => state.brands);
|
||||
const { currentCampaign } = useSelector((state) => state.campaign);
|
||||
|
||||
const handleSort = (field) => {
|
||||
return;
|
||||
@ -263,7 +299,7 @@ function AcceptedCreators() {
|
||||
<Form.Check
|
||||
type='checkbox'
|
||||
checked={
|
||||
selectedCampaign?.creators?.length === publicCreators.length && publicCreators.length > 0
|
||||
currentCampaign?.creators?.length === publicCreators.length && publicCreators.length > 0
|
||||
}
|
||||
onChange={handleSelectAll}
|
||||
/>
|
||||
@ -294,7 +330,7 @@ function AcceptedCreators() {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{!selectedCampaign?.creators || selectedCampaign?.creators?.length <= 0 ? (
|
||||
{!currentCampaign?.creators || currentCampaign?.creators?.length <= 0 ? (
|
||||
<>
|
||||
<tr>
|
||||
<td colSpan='9' className='text-center py-4'>
|
||||
@ -308,11 +344,11 @@ function AcceptedCreators() {
|
||||
</tr>
|
||||
</>
|
||||
) : (
|
||||
selectedCampaign?.creators?.map((creator) => (
|
||||
currentCampaign?.creators?.map((creator) => (
|
||||
<tr
|
||||
key={creator.creator_id}
|
||||
className={
|
||||
selectedCampaign?.creators?.includes(creator.creator_id) ? 'selected' : ''
|
||||
currentCampaign?.creators?.includes(creator.creator_id) ? 'selected' : ''
|
||||
}
|
||||
>
|
||||
{/* <td>
|
||||
@ -404,6 +440,7 @@ function StepProgress({ dates = [] }) {
|
||||
}
|
||||
|
||||
function MatchingResult() {
|
||||
const { currentCampaign } = useSelector((state) => state.campaign);
|
||||
const [showMatchingResultModal, setShowMatchingResultModal] = useState(false);
|
||||
|
||||
const mockData = [
|
||||
@ -468,18 +505,7 @@ function MatchingResult() {
|
||||
}
|
||||
|
||||
function CampaignMatchingResult({ show, onHide }) {
|
||||
const mockData = [
|
||||
{
|
||||
creator: 'Creator 1',
|
||||
category: 'Category 1',
|
||||
followers: 100,
|
||||
gmv: 100,
|
||||
avg_video_views: 100,
|
||||
status: 'Status 1',
|
||||
pricing: 100,
|
||||
profile: 'Profile 1',
|
||||
},
|
||||
];
|
||||
const { currentCampaign } = useSelector((state) => state.campaign);
|
||||
|
||||
return (
|
||||
<Modal show={show} onHide={onHide} size='lg'>
|
||||
@ -503,22 +529,22 @@ function CampaignMatchingResult({ show, onHide }) {
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{mockData.length > 0 ? (
|
||||
mockData.map((item, index) => (
|
||||
{currentCampaign?.matching_result?.length > 0 ? (
|
||||
currentCampaign?.matching_result?.map((item, index) => (
|
||||
<tr key={index}>
|
||||
<td>{item.creator}</td>
|
||||
<td>{item.name}</td>
|
||||
<td>{item.category}</td>
|
||||
<td>{item.followers}</td>
|
||||
<td>{item.gmv}</td>
|
||||
<td>{item.avg_video_views}</td>
|
||||
<td>{item.status}</td>
|
||||
<td>{item.status || '--'}</td>
|
||||
<td>{item.pricing}</td>
|
||||
<td>{item.profile}</td>
|
||||
<td>{determinProfileIcon(item)}</td>
|
||||
</tr>
|
||||
))
|
||||
) : (
|
||||
<tr>
|
||||
<td colSpan={6} className='text-center'>
|
||||
<td colSpan={8} className='text-center'>
|
||||
No data
|
||||
</td>
|
||||
</tr>
|
||||
|
@ -7,15 +7,15 @@ import ProductDetail, { CampaignsCollabCreators } from '../components/ProductDet
|
||||
|
||||
export default function CampaignScript() {
|
||||
const dispatch = useDispatch();
|
||||
const selectedProduct = useSelector((state) => state.brands.selectedProduct);
|
||||
const { currentProduct } = useSelector((state) => state.products);
|
||||
const [activeTab, setActiveTab] = useState('collaborationInfo');
|
||||
|
||||
useEffect(() => {
|
||||
console.log(selectedProduct);
|
||||
}, [selectedProduct]);
|
||||
console.log(currentProduct);
|
||||
}, [currentProduct]);
|
||||
|
||||
return (
|
||||
selectedProduct?.id && (
|
||||
currentProduct?.id && (
|
||||
<div className='product-script'>
|
||||
<ProductDetail />
|
||||
<div className='product-script-switches tab-switches'>
|
||||
|
@ -86,7 +86,7 @@ export default function Login() {
|
||||
/>
|
||||
</InputGroup>
|
||||
</Form.Group>
|
||||
<Button type='submit' loading={isLoading} disabled={!handleValidate()}>
|
||||
<Button type='submit' disabled={!handleValidate()}>
|
||||
Sign In
|
||||
</Button>
|
||||
</Form>
|
||||
|
@ -10,6 +10,7 @@ import discoveryReducer from './slices/discoverySlice';
|
||||
import notificationBarReducer from './slices/notificationBarSlice';
|
||||
import productReducer from './slices/productSlice';
|
||||
import chatReducer from './slices/chatSlice';
|
||||
import campaignReducer from './slices/campaignSlice';
|
||||
|
||||
const authPersistConfig = {
|
||||
key: 'auth',
|
||||
@ -26,6 +27,7 @@ const rootReducer = combineReducers({
|
||||
notificationBar: notificationBarReducer,
|
||||
products: productReducer,
|
||||
chat: chatReducer,
|
||||
campaign: campaignReducer,
|
||||
});
|
||||
|
||||
const store = configureStore({
|
||||
|
@ -143,7 +143,9 @@ export const fetchBrandDetail = createAsyncThunk('brands/fetchBrandDetail', asyn
|
||||
return rejectWithValue(error.message);
|
||||
}
|
||||
});
|
||||
export const fetchBrandCampaigns = createAsyncThunk('brands/fetchBrandCampaigns', async (brandId, { rejectWithValue }) => {
|
||||
export const fetchBrandCampaigns = createAsyncThunk(
|
||||
'brands/fetchBrandCampaigns',
|
||||
async (brandId, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await api.get(`/brands/${brandId}/campaigns/`);
|
||||
console.log(response);
|
||||
@ -154,21 +156,12 @@ export const fetchBrandCampaigns = createAsyncThunk('brands/fetchBrandCampaigns'
|
||||
} catch (error) {
|
||||
return rejectWithValue(error.message);
|
||||
}
|
||||
});
|
||||
|
||||
export const fetchCampaignDetail = createAsyncThunk('brands/fetchCampaignDetail', async (campaignId, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await api.get(`/campaigns/${campaignId}/`);
|
||||
if (response.code === 200) {
|
||||
return response.data;
|
||||
}
|
||||
throw new Error(response.message);
|
||||
} catch (error) {
|
||||
return rejectWithValue(error.message);
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
export const fetchBrandProducts = createAsyncThunk('brands/fetchBrandProducts', async (brandId, { rejectWithValue }) => {
|
||||
export const fetchBrandProducts = createAsyncThunk(
|
||||
'brands/fetchBrandProducts',
|
||||
async (brandId, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await api.get(`/brands/${brandId}/products/`);
|
||||
if (response.code !== 200) {
|
||||
@ -178,19 +171,8 @@ export const fetchBrandProducts = createAsyncThunk('brands/fetchBrandProducts',
|
||||
} catch (error) {
|
||||
return rejectWithValue(error.message);
|
||||
}
|
||||
});
|
||||
|
||||
export const fetchCampaigns = createAsyncThunk('brands/fetchCampaigns', async (_, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await api.get('/campaigns/');
|
||||
if (response.code !== 200) {
|
||||
throw new Error(response.message);
|
||||
}
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
return rejectWithValue(error.message);
|
||||
}
|
||||
});
|
||||
);
|
||||
|
||||
export const createBrandThunk = createAsyncThunk('brands/createBrand', async (brand, { rejectWithValue, dispatch }) => {
|
||||
try {
|
||||
@ -204,7 +186,9 @@ export const createBrandThunk = createAsyncThunk('brands/createBrand', async (br
|
||||
}
|
||||
});
|
||||
|
||||
export const createCampaignThunk = createAsyncThunk('brands/createCampaign', async (campaign, { rejectWithValue, dispatch }) => {
|
||||
export const createCampaignThunk = createAsyncThunk(
|
||||
'brands/createCampaign',
|
||||
async (campaign, { rejectWithValue, dispatch }) => {
|
||||
try {
|
||||
const response = await api.post('/campaigns/', campaign);
|
||||
console.log(response);
|
||||
@ -216,16 +200,15 @@ export const createCampaignThunk = createAsyncThunk('brands/createCampaign', asy
|
||||
dispatch(setNotificationBarMessage({ message: error.message, type: 'error' }));
|
||||
return rejectWithValue(error.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
const initialState = {
|
||||
brands: [],
|
||||
campaigns: [],
|
||||
status: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed'
|
||||
error: null,
|
||||
selectedBrand: {},
|
||||
selectedCampaign: {},
|
||||
selectedProduct: {},
|
||||
selectedBrand: null,
|
||||
};
|
||||
|
||||
const brandsSlice = createSlice({
|
||||
@ -238,16 +221,6 @@ const brandsSlice = createSlice({
|
||||
findBrandById: (state, action) => {
|
||||
state.selectedBrand = state.brands.find((brand) => brand.id.toString() === action.payload);
|
||||
},
|
||||
findCampaignById: (state, action) => {
|
||||
const { brandId, campaignId } = action.payload;
|
||||
const brand = state.brands?.find((b) => b.id.toString() === brandId);
|
||||
|
||||
state.selectedBrand = brand;
|
||||
state.selectedCampaign = brand?.campaigns?.find((c) => c.id.toString() === campaignId) || {};
|
||||
},
|
||||
setSelectedProduct: (state, action) => {
|
||||
state.selectedProduct = action.payload;
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder
|
||||
@ -284,17 +257,6 @@ const brandsSlice = createSlice({
|
||||
state.status = 'failed';
|
||||
state.error = action.error.message;
|
||||
})
|
||||
.addCase(fetchCampaigns.pending, (state) => {
|
||||
state.status = 'loading';
|
||||
})
|
||||
.addCase(fetchCampaigns.fulfilled, (state, action) => {
|
||||
state.status = 'succeeded';
|
||||
state.campaigns = action.payload;
|
||||
})
|
||||
.addCase(fetchCampaigns.rejected, (state, action) => {
|
||||
state.status = 'failed';
|
||||
state.error = action.error.message;
|
||||
})
|
||||
.addCase(createBrandThunk.pending, (state) => {
|
||||
state.status = 'loading';
|
||||
})
|
||||
@ -316,17 +278,6 @@ const brandsSlice = createSlice({
|
||||
state.status = 'failed';
|
||||
state.error = action.error.message;
|
||||
})
|
||||
.addCase(fetchCampaignDetail.pending, (state) => {
|
||||
state.status = 'loading';
|
||||
})
|
||||
.addCase(fetchCampaignDetail.fulfilled, (state, action) => {
|
||||
state.status = 'succeeded';
|
||||
state.selectedCampaign = action.payload;
|
||||
})
|
||||
.addCase(fetchCampaignDetail.rejected, (state, action) => {
|
||||
state.status = 'failed';
|
||||
state.error = action.error.message;
|
||||
})
|
||||
.addCase(createCampaignThunk.pending, (state) => {
|
||||
state.status = 'loading';
|
||||
})
|
||||
@ -351,6 +302,6 @@ const brandsSlice = createSlice({
|
||||
},
|
||||
});
|
||||
|
||||
export const { selectBrand, findBrandById, findCampaignById, setSelectedProduct } = brandsSlice.actions;
|
||||
export const { selectBrand, findBrandById, setSelectedProduct } = brandsSlice.actions;
|
||||
|
||||
export default brandsSlice.reducer;
|
||||
|
97
src/store/slices/campaignSlice.js
Normal file
97
src/store/slices/campaignSlice.js
Normal file
@ -0,0 +1,97 @@
|
||||
import { createAsyncThunk, createSlice } from "@reduxjs/toolkit";
|
||||
import api from "@/services/api";
|
||||
|
||||
export const fetchCampaigns = createAsyncThunk('brands/fetchCampaigns', async (_, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await api.get('/campaigns/');
|
||||
if (response.code !== 200) {
|
||||
throw new Error(response.message);
|
||||
}
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
return rejectWithValue(error.message);
|
||||
}
|
||||
});
|
||||
|
||||
export const fetchCampaignDetail = createAsyncThunk('brands/fetchCampaignDetail', async (campaignId, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await api.get(`/campaigns/${campaignId}/`);
|
||||
if (response.code === 200) {
|
||||
return response.data;
|
||||
}
|
||||
throw new Error(response.message);
|
||||
} catch (error) {
|
||||
return rejectWithValue(error.message);
|
||||
}
|
||||
});
|
||||
|
||||
export const fetchMatchingResult = createAsyncThunk('brands/fetchMatchingResult', async (query, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await api.post('/operation/sql_search/', query);
|
||||
if (response.code !== 200) {
|
||||
throw new Error(response.message);
|
||||
}
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
return rejectWithValue(error.message);
|
||||
}
|
||||
});
|
||||
|
||||
const initialState = {
|
||||
campaigns: [],
|
||||
currentCampaign: null,
|
||||
status: 'idle',
|
||||
error: null,
|
||||
};
|
||||
|
||||
const campaignSlice = createSlice({
|
||||
name: 'campaign',
|
||||
initialState,
|
||||
reducers: {
|
||||
setCurrentCampaign: (state, action) => {
|
||||
state.currentCampaign = action.payload;
|
||||
},
|
||||
resetCurrentCampaign: (state) => {
|
||||
state.currentCampaign = null;
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder
|
||||
.addCase(fetchCampaigns.pending, (state) => {
|
||||
state.status = 'loading';
|
||||
})
|
||||
.addCase(fetchCampaigns.fulfilled, (state, action) => {
|
||||
state.status = 'succeeded';
|
||||
state.campaigns = action.payload;
|
||||
})
|
||||
.addCase(fetchCampaigns.rejected, (state, action) => {
|
||||
state.status = 'failed';
|
||||
state.error = action.payload;
|
||||
})
|
||||
.addCase(fetchCampaignDetail.pending, (state) => {
|
||||
state.status = 'loading';
|
||||
})
|
||||
.addCase(fetchCampaignDetail.fulfilled, (state, action) => {
|
||||
state.status = 'succeeded';
|
||||
state.currentCampaign = action.payload;
|
||||
})
|
||||
.addCase(fetchCampaignDetail.rejected, (state, action) => {
|
||||
state.status = 'failed';
|
||||
state.error = action.payload;
|
||||
})
|
||||
.addCase(fetchMatchingResult.pending, (state) => {
|
||||
state.status = 'loading';
|
||||
})
|
||||
.addCase(fetchMatchingResult.fulfilled, (state, action) => {
|
||||
state.status = 'succeeded';
|
||||
state.currentCampaign.matching_result = action.payload.results;
|
||||
})
|
||||
.addCase(fetchMatchingResult.rejected, (state, action) => {
|
||||
state.status = 'failed';
|
||||
state.error = action.payload;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const { setCurrentCampaign, resetCurrentCampaign } = campaignSlice.actions;
|
||||
export default campaignSlice.reducer;
|
@ -14,7 +14,9 @@ export const fetchProducts = createAsyncThunk('products/fetchProducts', async (_
|
||||
}
|
||||
});
|
||||
|
||||
export const addProductToCampaign = createAsyncThunk('products/addProductToCampaign', async (formData, { rejectWithValue, dispatch }) => {
|
||||
export const addProductToCampaign = createAsyncThunk(
|
||||
'products/addProductToCampaign',
|
||||
async (formData, { rejectWithValue, dispatch }) => {
|
||||
try {
|
||||
const { campaignId, productId } = formData;
|
||||
const response = await api.post(`/campaigns/${campaignId}/add_product/`, { product_id: productId });
|
||||
@ -27,9 +29,12 @@ export const addProductToCampaign = createAsyncThunk('products/addProductToCampa
|
||||
dispatch(setNotificationBarMessage({ message: error.message, type: 'error' }));
|
||||
return rejectWithValue(error.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
export const fetchProductDetail = createAsyncThunk('products/fetchProductDetail', async (productId, { rejectWithValue, dispatch }) => {
|
||||
export const fetchProductDetail = createAsyncThunk(
|
||||
'products/fetchProductDetail',
|
||||
async (productId, { rejectWithValue, dispatch }) => {
|
||||
try {
|
||||
const response = await api.get(`/products/${productId}/`);
|
||||
if (response.code !== 200) {
|
||||
@ -40,54 +45,115 @@ export const fetchProductDetail = createAsyncThunk('products/fetchProductDetail'
|
||||
dispatch(setNotificationBarMessage({ message: error.message, type: 'error' }));
|
||||
return rejectWithValue(error.message);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export const fetchProductCampaigns = createAsyncThunk('products/fetchProductCampaigns', async (productId, { rejectWithValue, dispatch }) => {
|
||||
try {
|
||||
const response = await api.get('/campaigns/by-product/', { params: { product_id: productId } });
|
||||
if (response.code !== 200) {
|
||||
throw new Error(response.message);
|
||||
}
|
||||
if (response.data.campaigns.length > 0) {
|
||||
response.data.campaigns.forEach((campaign) => {
|
||||
dispatch(fetchCampaignCreators(campaign.id));
|
||||
});
|
||||
}
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
return rejectWithValue(error.message);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
export const fetchCampaignCreators = createAsyncThunk('products/fetchCampaignCreators', async (campaignId, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await api.get(`/campaigns/${campaignId}/creator_list/`);
|
||||
if (response.code !== 200) {
|
||||
throw new Error(response.message);
|
||||
}
|
||||
return { campaignId, creators: response.data.creators };
|
||||
} catch (error) {
|
||||
return rejectWithValue(error.message);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
const initialState = {
|
||||
products: [],
|
||||
loading: false,
|
||||
status: 'idle', // idle, loading, success, error
|
||||
error: null,
|
||||
productDetail: null,
|
||||
currentProduct: null,
|
||||
};
|
||||
|
||||
const productSlice = createSlice({
|
||||
name: 'products',
|
||||
initialState,
|
||||
reducers: {},
|
||||
reducers: {
|
||||
setCurrentProduct: (state, action) => {
|
||||
state.currentProduct = action.payload;
|
||||
},
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(fetchProducts.pending, (state) => {
|
||||
state.loading = true;
|
||||
state.status = 'loading';
|
||||
});
|
||||
builder.addCase(fetchProducts.fulfilled, (state, action) => {
|
||||
state.products = action.payload;
|
||||
state.loading = false;
|
||||
})
|
||||
state.status = 'success';
|
||||
});
|
||||
builder.addCase(fetchProducts.rejected, (state, action) => {
|
||||
state.error = action.payload;
|
||||
state.loading = false;
|
||||
})
|
||||
state.status = 'error';
|
||||
});
|
||||
builder.addCase(addProductToCampaign.pending, (state) => {
|
||||
state.loading = true;
|
||||
})
|
||||
state.status = 'loading';
|
||||
});
|
||||
builder.addCase(addProductToCampaign.fulfilled, (state, action) => {
|
||||
state.products = action.payload;
|
||||
state.loading = false;
|
||||
})
|
||||
state.status = 'success';
|
||||
});
|
||||
builder.addCase(addProductToCampaign.rejected, (state, action) => {
|
||||
state.error = action.payload;
|
||||
state.loading = false;
|
||||
})
|
||||
state.status = 'error';
|
||||
});
|
||||
builder.addCase(fetchProductDetail.pending, (state) => {
|
||||
state.loading = true;
|
||||
})
|
||||
state.status = 'loading';
|
||||
});
|
||||
builder.addCase(fetchProductDetail.fulfilled, (state, action) => {
|
||||
state.productDetail = action.payload;
|
||||
state.loading = false;
|
||||
})
|
||||
state.status = 'success';
|
||||
});
|
||||
builder.addCase(fetchProductDetail.rejected, (state, action) => {
|
||||
state.error = action.payload;
|
||||
state.loading = false;
|
||||
})
|
||||
state.status = 'error';
|
||||
});
|
||||
builder.addCase(fetchProductCampaigns.pending, (state) => {
|
||||
state.status = 'loading';
|
||||
});
|
||||
builder.addCase(fetchProductCampaigns.fulfilled, (state, action) => {
|
||||
state.currentProduct.campaigns = action.payload.campaigns || [];
|
||||
state.status = 'success';
|
||||
});
|
||||
builder.addCase(fetchProductCampaigns.rejected, (state, action) => {
|
||||
state.error = action.payload;
|
||||
state.status = 'error';
|
||||
});
|
||||
builder.addCase(fetchCampaignCreators.pending, (state) => {
|
||||
state.status = 'loading';
|
||||
});
|
||||
builder.addCase(fetchCampaignCreators.fulfilled, (state, action) => {
|
||||
state.currentProduct.campaigns.find((campaign) => campaign.id === action.payload.campaignId).creators = action.payload.creators;
|
||||
state.status = 'success';
|
||||
});
|
||||
builder.addCase(fetchCampaignCreators.rejected, (state, action) => {
|
||||
state.error = action.payload;
|
||||
state.status = 'error';
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export const { setCurrentProduct } = productSlice.actions;
|
||||
|
||||
export default productSlice.reducer;
|
||||
|
@ -197,7 +197,7 @@
|
||||
.campaigns-list {
|
||||
display: flex;
|
||||
gap: 0.875rem;
|
||||
|
||||
flex-flow: row wrap;
|
||||
.campaign-info {
|
||||
background-color: #fff;
|
||||
border-radius: 0.375rem;
|
||||
@ -206,7 +206,7 @@
|
||||
margin-bottom: 1rem;
|
||||
border: 2px solid transparent;
|
||||
transition: 0.25s;
|
||||
width: 350px;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
gap: 0.5rem;
|
||||
|
Loading…
Reference in New Issue
Block a user