mirror of
https://github.com/Funkoala14/CreatorCenter_OOIN.git
synced 2025-06-07 12:28:15 +08:00
[dev]campaign&brand detail
This commit is contained in:
parent
dba045e0fe
commit
7549e0f47b
@ -27,8 +27,9 @@ export default function CampaignInfo() {
|
||||
Category
|
||||
</div>
|
||||
<div className='campaign-info-item-value'>
|
||||
{selectedCampaign?.category?.length > 0 &&
|
||||
selectedCampaign.category.map((cat,index) => <span className='category-tag' key={index}>{cat}</span>)}
|
||||
{selectedCampaign?.creator_category || '--'}
|
||||
{/* {selectedCampaign?.category?.length > 0 &&
|
||||
selectedCampaign.category.map((cat,index) => <span className='category-tag' key={index}>{cat}</span>)} */}
|
||||
</div>
|
||||
</div>
|
||||
<div className='campaign-info-item'>
|
||||
@ -57,7 +58,7 @@ export default function CampaignInfo() {
|
||||
<CircleDollarSign size={18} />
|
||||
Pricing
|
||||
</div>
|
||||
<div className='campaign-info-item-value'>{selectedCampaign?.pricing || '--'}</div>
|
||||
<div className='campaign-info-item-value'>{selectedCampaign?.budget || '--'}</div>
|
||||
</div>
|
||||
<div className='campaign-info-item'>
|
||||
<div className='campaign-info-item-label'>
|
||||
@ -65,10 +66,11 @@ export default function CampaignInfo() {
|
||||
Creator Level
|
||||
</div>
|
||||
<div className='campaign-info-item-value'>
|
||||
{selectedCampaign?.creator_level?.length > 0 &&
|
||||
{selectedCampaign?.creator_level || '--'}
|
||||
{/* {selectedCampaign?.creator_level?.length > 0 &&
|
||||
selectedCampaign.creator_level.map((level,index) => (
|
||||
<span className='creator-level-tag' key={index}>{level}</span>
|
||||
))}
|
||||
))} */}
|
||||
</div>
|
||||
</div>
|
||||
<div className='campaign-info-item'>
|
||||
@ -83,7 +85,7 @@ export default function CampaignInfo() {
|
||||
<Hash size={18} />
|
||||
Creators
|
||||
</div>
|
||||
<div className='campaign-info-item-value'>{selectedCampaign?.creators || '--'}</div>
|
||||
<div className='campaign-info-item-value'>{selectedCampaign?.creators_count || '--'}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,17 +1,18 @@
|
||||
import React from 'react';
|
||||
import { useEffect } from 'react';
|
||||
import { useSelector } from 'react-redux';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Link, useParams } from 'react-router-dom';
|
||||
import { ChartNoAxesColumnIncreasing, CircleDollarSign, Edit, Eye, Folders, Hash, Layers, Tag, TrendingUp, UserRoundCheck } from 'lucide-react';
|
||||
import Spinning from './Spinning';
|
||||
|
||||
export default function CampaignList() {
|
||||
const { selectedBrand ,status } = useSelector((state) => state.brands);
|
||||
|
||||
useEffect(() => {
|
||||
console.log(selectedBrand);
|
||||
}, [selectedBrand]);
|
||||
|
||||
|
||||
|
||||
if (status === 'loading') {
|
||||
return <Spinning />;
|
||||
}
|
||||
@ -34,26 +35,28 @@ export default function CampaignList() {
|
||||
</div>
|
||||
<div className='campaign-item'>
|
||||
<div className='campaign-item-label'>Creator Type</div>
|
||||
<div className='campaign-item-value'>{campaign.creatorType}</div>
|
||||
<div className='campaign-item-value'>{campaign.creator_type}</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 className='campaign-item-value'>{campaign.creators_count}</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}
|
||||
{/* {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>)}
|
||||
{campaign.creator_category}
|
||||
{/* {campaign.category &&
|
||||
campaign.category.map((cat) => <span className='category-tag'>{cat}</span>)} */}
|
||||
</div>
|
||||
</div>
|
||||
<div className='campaign-item'>
|
||||
|
@ -5,8 +5,9 @@ import { Button, Modal, Form } from 'react-bootstrap';
|
||||
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';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { createBrandThunk, fetchBrands, selectBrand } from '../store/slices/brandsSlice';
|
||||
import SpinningComponent from '../components/Spinning';
|
||||
|
||||
export default function Brands() {
|
||||
const navigate = useNavigate();
|
||||
@ -43,16 +44,35 @@ function AddBrandModal({ show, onHide }) {
|
||||
const [brandName, setBrandName] = useState('');
|
||||
const [brandSource, setBrandSource] = useState('');
|
||||
const [campaignId, setCampaignId] = useState('');
|
||||
const [validated, setValidated] = useState(false);
|
||||
const dispatch = useDispatch();
|
||||
const { status, error } = useSelector((state) => state.brands);
|
||||
|
||||
const sourceOptions = [
|
||||
{ value: '1', label: 'Third Party Agency' },
|
||||
{ value: '2', label: 'Official Event' },
|
||||
{ value: '3', label: 'Social Media' },
|
||||
{ value: '1', label: 'TKS Official' },
|
||||
{ value: '2', label: 'Third Party Agency' },
|
||||
{ value: '3', label: 'Official Event' },
|
||||
{ value: '4', label: 'Social Media' },
|
||||
];
|
||||
|
||||
const handleCreateBrand = () => {
|
||||
console.log(brandName, brandSource, campaignId);
|
||||
handleCancel();
|
||||
const handleSubmit = async (e) => {
|
||||
const form = document.getElementById('brandForm');
|
||||
if (form.checkValidity() === false) {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
setValidated(true);
|
||||
return;
|
||||
}
|
||||
const brand = {
|
||||
name: brandName,
|
||||
source: brandSource,
|
||||
campaign_id: campaignId,
|
||||
};
|
||||
await dispatch(createBrandThunk(brand)).unwrap();
|
||||
if (status === 'succeeded') {
|
||||
dispatch(fetchBrands());
|
||||
handleCancel();
|
||||
}
|
||||
};
|
||||
|
||||
const handleCancel = () => {
|
||||
@ -62,17 +82,28 @@ function AddBrandModal({ show, onHide }) {
|
||||
setCampaignId('');
|
||||
};
|
||||
|
||||
if (status === 'loading') {
|
||||
return <SpinningComponent />;
|
||||
}
|
||||
|
||||
return (
|
||||
<Modal show={show} onHide={onHide} backdropClassName='modal-backdrop' container={document.body}>
|
||||
<Modal.Header>
|
||||
<Modal.Title>Add Brand</Modal.Title>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<Form onSubmit={handleCreateBrand} className='add-brand-form'>
|
||||
<Form
|
||||
id='brandForm'
|
||||
noValidate
|
||||
validated={validated}
|
||||
onSubmit={handleSubmit}
|
||||
className='add-brand-form'
|
||||
>
|
||||
<Form.Group className='mb-3' controlId='formBasicName'>
|
||||
<Form.Label>BrandName</Form.Label>
|
||||
<Form.Control
|
||||
type='text'
|
||||
required
|
||||
placeholder='Enter name'
|
||||
value={brandName}
|
||||
onChange={(e) => setBrandName(e.target.value)}
|
||||
@ -80,7 +111,7 @@ function AddBrandModal({ show, onHide }) {
|
||||
</Form.Group>
|
||||
<Form.Group className='mb-3' controlId='formBasicSource'>
|
||||
<Form.Label>Source</Form.Label>
|
||||
<Form.Select value={brandSource} onChange={(e) => setBrandSource(e.target.value)}>
|
||||
<Form.Select value={brandSource} onChange={(e) => setBrandSource(e.target.value)} required>
|
||||
{sourceOptions.map((option) => (
|
||||
<option key={option.value} value={option.value}>
|
||||
{option.label}
|
||||
@ -92,6 +123,7 @@ function AddBrandModal({ show, onHide }) {
|
||||
<Form.Label>Campaign ID</Form.Label>
|
||||
<Form.Control
|
||||
type='text'
|
||||
required
|
||||
placeholder='Enter campaign ID'
|
||||
value={campaignId}
|
||||
onChange={(e) => setCampaignId(e.target.value)}
|
||||
@ -101,7 +133,7 @@ function AddBrandModal({ show, onHide }) {
|
||||
<Button variant='outline-light' className='text-primary' onClick={handleCancel}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button variant='primary' onClick={handleCreateBrand}>
|
||||
<Button variant='primary' onClick={handleSubmit}>
|
||||
Create
|
||||
</Button>
|
||||
</div>
|
||||
|
@ -6,7 +6,7 @@ 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, fetchBrandCampaigns, fetchBrandProducts } from '../store/slices/brandsSlice';
|
||||
import { fetchBrandDetail, fetchBrandCampaigns, fetchBrandProducts } from '../store/slices/brandsSlice';
|
||||
|
||||
export default function BrandsDetail() {
|
||||
const { id } = useParams();
|
||||
@ -16,7 +16,7 @@ export default function BrandsDetail() {
|
||||
|
||||
useEffect(() => {
|
||||
if (id) {
|
||||
dispatch(findBrandById(id));
|
||||
dispatch(fetchBrandDetail(id));
|
||||
dispatch(fetchBrandCampaigns(id));
|
||||
dispatch(fetchBrandProducts(id));
|
||||
}
|
||||
@ -114,7 +114,7 @@ export default function BrandsDetail() {
|
||||
</div>
|
||||
</div>
|
||||
{activeTab === 'campaigns' && <CampaignList />}
|
||||
{activeTab === 'products' && <ProductsList />}
|
||||
{activeTab === 'products' && <ProductsList onShowProductDetail={() => {}} />}
|
||||
</React.Fragment>
|
||||
)
|
||||
);
|
||||
|
@ -3,7 +3,7 @@ import { Link, useParams } from 'react-router-dom';
|
||||
import SearchBar from '../components/SearchBar';
|
||||
import { Button, Form, Modal } from 'react-bootstrap';
|
||||
import { useSelector, useDispatch } from 'react-redux';
|
||||
import { fetchBrands, findCampaignById, findProductById } from '../store/slices/brandsSlice';
|
||||
import { fetchBrands, fetchBrandDetail, findProductById, fetchCampaignDetail } from '../store/slices/brandsSlice';
|
||||
import CampaignInfo from '../components/CampaignInfo';
|
||||
import { ChevronRight, Send, Plus } from 'lucide-react';
|
||||
import ProductsList from '../components/ProductsList';
|
||||
@ -26,7 +26,12 @@ export default function CampaignDetail() {
|
||||
|
||||
useEffect(() => {
|
||||
if (brandId && campaignId) {
|
||||
dispatch(findCampaignById({ brandId, campaignId }));
|
||||
if (!selectedBrand) {
|
||||
dispatch(fetchBrandDetail(brandId));
|
||||
dispatch(fetchCampaignDetail(campaignId));
|
||||
} else {
|
||||
dispatch(fetchCampaignDetail(campaignId));
|
||||
}
|
||||
}
|
||||
}, [brandId, campaignId]);
|
||||
|
||||
@ -151,7 +156,9 @@ export default function CampaignDetail() {
|
||||
function AddProductModal({ show, onHide }) {
|
||||
return (
|
||||
<Modal show={show} onHide={onHide}>
|
||||
<Modal.Header closeButton className='fw-bold'>Add Product</Modal.Header>
|
||||
<Modal.Header closeButton className='fw-bold'>
|
||||
Add Product
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
<Form className='add-product-form'>
|
||||
<Form.Group className='mb-3'>
|
||||
|
@ -106,27 +106,89 @@ const mockBrands = [
|
||||
},
|
||||
];
|
||||
|
||||
export const fetchBrands = createAsyncThunk('brands/fetchBrands', async () => {
|
||||
// const response = await fetch('https://api.example.com/brands');
|
||||
const response = await api.get('/brands/');
|
||||
return response.data;
|
||||
export const fetchBrands = createAsyncThunk('brands/fetchBrands', async (_, { rejectWithValue, dispatch }) => {
|
||||
try {
|
||||
const response = await api.get('/brands/');
|
||||
if (response.code !== 200) {
|
||||
throw new Error(response.message);
|
||||
}
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
dispatch(setNotificationBarMessage({ message: error.message, type: 'error' }));
|
||||
return rejectWithValue(error.message);
|
||||
}
|
||||
});
|
||||
|
||||
export const fetchBrandCampaigns = createAsyncThunk('brands/fetchBrandCampaigns', async (brandId) => {
|
||||
const response = await api.get(`/brands/${brandId}/campaigns/`);
|
||||
return response.data;
|
||||
export const fetchBrandDetail = createAsyncThunk('brands/fetchBrandDetail', async (brandId, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await api.get(`/brands/${brandId}/`);
|
||||
if (response.code === 200) {
|
||||
return response.data;
|
||||
}
|
||||
throw new Error(response.message);
|
||||
} catch (error) {
|
||||
return rejectWithValue(error.message);
|
||||
}
|
||||
});
|
||||
export const fetchBrandCampaigns = createAsyncThunk('brands/fetchBrandCampaigns', async (brandId, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await api.get(`/brands/${brandId}/campaigns/`);
|
||||
console.log(response);
|
||||
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) => {
|
||||
const response = await api.get(`/brands/${brandId}/products/`);
|
||||
console.log(response);
|
||||
return response.data;
|
||||
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 fetchCampaigns = createAsyncThunk('brands/fetchCampaigns', async () => {
|
||||
const response = await api.get('/campaigns/');
|
||||
console.log(response);
|
||||
return response.data;
|
||||
export const fetchBrandProducts = createAsyncThunk('brands/fetchBrandProducts', async (brandId, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await api.get(`/brands/${brandId}/products/`);
|
||||
if (response.code !== 200) {
|
||||
throw new Error(response.message);
|
||||
}
|
||||
return response.data;
|
||||
} 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 {
|
||||
const response = await api.post('/brands/', brand);
|
||||
if (response.code !== 201 && response.code !== 200) {
|
||||
throw new Error(response.message);
|
||||
}
|
||||
} catch (error) {
|
||||
dispatch(setNotificationBarMessage({ message: error.message, type: 'error' }));
|
||||
return rejectWithValue(error.message);
|
||||
}
|
||||
});
|
||||
|
||||
const initialState = {
|
||||
@ -210,6 +272,38 @@ const brandsSlice = createSlice({
|
||||
.addCase(fetchCampaigns.rejected, (state, action) => {
|
||||
state.status = 'failed';
|
||||
state.error = action.error.message;
|
||||
})
|
||||
.addCase(createBrandThunk.pending, (state) => {
|
||||
state.status = 'loading';
|
||||
})
|
||||
.addCase(createBrandThunk.fulfilled, (state, action) => {
|
||||
state.status = 'succeeded';
|
||||
})
|
||||
.addCase(createBrandThunk.rejected, (state, action) => {
|
||||
state.status = 'failed';
|
||||
state.error = action.error.message;
|
||||
})
|
||||
.addCase(fetchBrandDetail.pending, (state) => {
|
||||
state.status = 'loading';
|
||||
})
|
||||
.addCase(fetchBrandDetail.fulfilled, (state, action) => {
|
||||
state.status = 'succeeded';
|
||||
state.selectedBrand = action.payload;
|
||||
})
|
||||
.addCase(fetchBrandDetail.rejected, (state, action) => {
|
||||
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;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
@ -10,6 +10,17 @@
|
||||
--bs-body-color: #171a1fff;
|
||||
--bs-btn-color: white !important;
|
||||
--bs-btn-hover-color: white !important;
|
||||
--bs-form-valid-border-color: #6366f1 !important;
|
||||
--bs-form-valid-bg-color: #6366f1 !important;
|
||||
--bs-form-valid-color: #6366f1 !important;
|
||||
}
|
||||
select:focus {
|
||||
box-shadow: 0 0 0 0.2rem rgba(99, 102, 241, 0.25) !important;
|
||||
outline: none;
|
||||
}
|
||||
input:focus {
|
||||
box-shadow: 0 0 0 0.2rem rgba(99, 102, 241, 0.25) !important;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
|
Loading…
Reference in New Issue
Block a user