[dev]campaign&brand detail

This commit is contained in:
susie-laptop 2025-05-23 21:23:39 -04:00
parent dba045e0fe
commit 7549e0f47b
7 changed files with 195 additions and 46 deletions

View File

@ -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>

View File

@ -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'>

View File

@ -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);
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>

View File

@ -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>
)
);

View File

@ -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'>

View File

@ -106,27 +106,89 @@ const mockBrands = [
},
];
export const fetchBrands = createAsyncThunk('brands/fetchBrands', async () => {
// const response = await fetch('https://api.example.com/brands');
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) => {
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) => {
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 }) => {
try {
const response = await api.get(`/brands/${brandId}/products/`);
console.log(response);
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 () => {
export const fetchCampaigns = createAsyncThunk('brands/fetchCampaigns', async (_, { rejectWithValue }) => {
try {
const response = await api.get('/campaigns/');
console.log(response);
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;
});
},
});

View File

@ -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 {