[dev]addtocampaign

This commit is contained in:
susie-laptop 2025-05-23 20:16:21 -04:00
parent 8dd91b75c8
commit dba045e0fe
10 changed files with 212 additions and 45 deletions

View File

@ -5,6 +5,7 @@ import Router from './router';
import './styles/Campaign.scss'; import './styles/Campaign.scss';
import '@/styles/custom-theme.scss'; import '@/styles/custom-theme.scss';
import { Chart as ChartJS, ArcElement, Tooltip, Legend, BarElement, LinearScale, CategoryScale } from 'chart.js'; import { Chart as ChartJS, ArcElement, Tooltip, Legend, BarElement, LinearScale, CategoryScale } from 'chart.js';
import NotificationBar from './components/NotificationBar';
ChartJS.register(ArcElement, Tooltip, Legend, BarElement, LinearScale, CategoryScale); ChartJS.register(ArcElement, Tooltip, Legend, BarElement, LinearScale, CategoryScale);
@ -12,7 +13,12 @@ ChartJS.register(ArcElement, Tooltip, Legend, BarElement, LinearScale, CategoryS
library.add(faTiktok, fas, faYoutube, faInstagram); library.add(faTiktok, fas, faYoutube, faInstagram);
function App() { function App() {
return <Router />; return (
<>
<Router />
<NotificationBar />
</>
);
} }
export default App; export default App;

View File

@ -0,0 +1,87 @@
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 SpinningComponent from './Spinning';
import { addCreatorsToCampaign } from '../store/slices/creatorsSlice';
import { setNotificationBarMessage } from '../store/slices/notificationBarSlice';
export default function AddToCampaign({ show, onHide }) {
const dispatch = useDispatch();
const { campaigns, status } = useSelector((state) => state.brands);
const { selectedCreators } = useSelector((state) => state.creators);
const [campaignId, setCampaignId] = useState(null);
const [validated, setValidated] = useState(false);
useEffect(() => {
if (show) {
dispatch(fetchCampaigns());
}
return () => {
setCampaignId(null);
setValidated(false);
};
}, [show, dispatch]);
const handleSubmit = async (e) => {
const form = document.getElementById('campaignForm');
if (form.checkValidity() === false) {
e.preventDefault();
e.stopPropagation();
setValidated(true);
return;
}
await dispatch(addCreatorsToCampaign({ creatorIds: selectedCreators, campaignId })).unwrap();
onHide();
};
if (status === 'loading') {
return <SpinningComponent />;
}
return (
<Modal show={show} onHide={onHide}>
<Modal.Header>
<Modal.Title>Add to Campaign</Modal.Title>
</Modal.Header>
<Modal.Body>
<Form id='campaignForm' noValidate validated={validated}>
<Form.Group>
<Form.Label>Campaign Name</Form.Label>
<Form.Select
type='select'
defaultValue=''
value={campaignId}
required
onChange={(e) => {
setCampaignId(e.target.value);
}}
>
<option value='' disabled selected>
Select Campaign
</option>
{campaigns.map((campaign) => (
<option key={campaign.id} value={campaign.id}>
{campaign.name}
</option>
))}
</Form.Select>
<Form.Control.Feedback type="invalid">
Please select a campaign.
</Form.Control.Feedback>
</Form.Group>
</Form>
</Modal.Body>
<Modal.Footer>
<Button variant='outline-primary' className='border-0' onClick={onHide}>
Cancel
</Button>
<Button variant='primary' onClick={handleSubmit}>
Add
</Button>
</Modal.Footer>
</Modal>
);
}

View File

@ -1,14 +1,33 @@
import { Check, CircleAlert, Info, X } from 'lucide-react'; import { Check, CircleAlert, Info, X } from 'lucide-react';
import { useDispatch } from 'react-redux'; import { useDispatch, useSelector } from 'react-redux';
import { resetNotificationBar } from '../store/slices/notificationBarSlice';
import { useEffect } from 'react';
export default function NotificationBar() { export default function NotificationBar() {
const { message, type, show } = useSelector((state) => state.notificationBar); const { message, type, show } = useSelector((state) => state.notificationBar);
const dispatch = useDispatch(); const dispatch = useDispatch();
const setTimeOut = () => {
setTimeout(() => {
dispatch(resetNotificationBar());
}, 3000);
};
const handleClose = () => {
dispatch(resetNotificationBar());
};
useEffect(() => {
console.log(message);
setTimeOut();
}, [message]);
return ( return (
show && (
<div <div
className={`snackbar alert alert-${type} d-flex align-items-center justify-content-between position-fixed top-10 start-50 translate-middle w-50 gap-2`} className={`snackbar alert alert-${type} d-flex align-items-center justify-content-between position-fixed top-5 start-50 translate-middle w-50 gap-2`}
role='alert' role='alert'
style={{ zIndex: 999999, top: '15%' }}
> >
{type === 'success' && <Check />} {type === 'success' && <Check />}
{type === 'warning' && <CircleAlert />} {type === 'warning' && <CircleAlert />}
@ -17,5 +36,6 @@ export default function NotificationBar() {
<div className='flex-fill'>{message}</div> <div className='flex-fill'>{message}</div>
<button type='button' className='btn-close flex-end' onClick={handleClose} aria-label='Close'></button> <button type='button' className='btn-close flex-end' onClick={handleClose} aria-label='Close'></button>
</div> </div>
)
); );
} }

View File

@ -1,6 +1,6 @@
import { Spinner } from "react-bootstrap"; import { Spinner } from "react-bootstrap";
export default function Spinning() { export default function SpinningComponent() {
return ( return (
<div className='text-center p-5'> <div className='text-center p-5'>
<Spinner animation='border' role='status' variant='primary'> <Spinner animation='border' role='status' variant='primary'>

View File

@ -1,14 +1,18 @@
import React from 'react'; import React, { useState } from 'react';
import DatabaseFilter from '../components/DatabaseFilter'; import DatabaseFilter from '../components/DatabaseFilter';
import CreatorList from '../components/CreatorList'; import CreatorList from '../components/CreatorList';
import SearchBar from '../components/SearchBar'; import SearchBar from '../components/SearchBar';
import { Button } from 'react-bootstrap'; import { Button } from 'react-bootstrap';
import AddToCampaign from '../components/AddToCampaign';
export default function Database({ path }) { export default function Database({ path }) {
const [showAddToCampaignModal, setShowAddToCampaignModal] = useState(false);
return ( return (
<div className='database-page'> <div className='database-page'>
<div className='function-bar'> <div className='function-bar'>
<SearchBar /> <SearchBar />
<Button>+ Add to Campaign</Button> <Button onClick={() => setShowAddToCampaignModal(true)}>+ Add to Campaign</Button>
</div> </div>
<div className='breadcrumb'> <div className='breadcrumb'>
<div className='breadcrumb-item'>Creator Database</div> <div className='breadcrumb-item'>Creator Database</div>
@ -18,6 +22,7 @@ export default function Database({ path }) {
</div> </div>
<DatabaseFilter path={path} pageType={'database'} /> <DatabaseFilter path={path} pageType={'database'} />
<CreatorList path={path} /> <CreatorList path={path} />
<AddToCampaign show={showAddToCampaignModal} onHide={() => setShowAddToCampaignModal(false)} onSubmit={() => setShowAddToCampaignModal(false)} />
</div> </div>
); );
} }

View File

@ -7,25 +7,25 @@ import sessionStorage from 'redux-persist/es/storage/session';
import inboxReducer from './slices/inboxSlice'; import inboxReducer from './slices/inboxSlice';
import authReducer from './slices/authSlice'; import authReducer from './slices/authSlice';
import discoveryReducer from './slices/discoverySlice'; import discoveryReducer from './slices/discoverySlice';
import notificationBarReducer from './slices/notificationBarSlice';
const reducers = combineReducers({ const authPersistConfig = {
key: 'auth',
storage: sessionStorage,
};
const rootReducer = combineReducers({
creators: creatorsReducer, creators: creatorsReducer,
filters: filtersReducer, filters: filtersReducer,
brands: brandsReducer, brands: brandsReducer,
inbox: inboxReducer, inbox: inboxReducer,
auth: authReducer,
discovery: discoveryReducer, discovery: discoveryReducer,
auth: persistReducer(authPersistConfig, authReducer),
notificationBar: notificationBarReducer,
}); });
const persistConfig = {
key: 'root',
storage: sessionStorage,
};
const persistedReducer = persistReducer(persistConfig, reducers);
const store = configureStore({ const store = configureStore({
reducer: persistedReducer, reducer: rootReducer,
middleware: (getDefaultMiddleware) => middleware: (getDefaultMiddleware) =>
getDefaultMiddleware({ getDefaultMiddleware({
serializableCheck: false, serializableCheck: false,

View File

@ -123,8 +123,15 @@ export const fetchBrandProducts = createAsyncThunk('brands/fetchBrandProducts',
return response.data; return response.data;
}); });
export const fetchCampaigns = createAsyncThunk('brands/fetchCampaigns', async () => {
const response = await api.get('/campaigns/');
console.log(response);
return response.data;
});
const initialState = { const initialState = {
brands: [], brands: [],
campaigns: [],
status: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed' status: 'idle', // 'idle' | 'loading' | 'succeeded' | 'failed'
error: null, error: null,
selectedBrand: {}, selectedBrand: {},
@ -192,6 +199,17 @@ const brandsSlice = createSlice({
.addCase(fetchBrandProducts.rejected, (state, action) => { .addCase(fetchBrandProducts.rejected, (state, action) => {
state.status = 'failed'; state.status = 'failed';
state.error = action.error.message; 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;
}); });
}, },
}); });

View File

@ -221,11 +221,11 @@ export const fetchCreators = createAsyncThunk(
async ({ page = 1 }, { getState, rejectWithValue }) => { async ({ page = 1 }, { getState, rejectWithValue }) => {
try { try {
const state = getState(); const state = getState();
const filters = state.filters; const { pricing, views_range, ...filter } = state.filters;
const { code, data, message, pagination } = await api.post( const { code, data, message, pagination } = await api.post(
`/daren_detail/public/creators/filter/?page=${page}`, `/daren_detail/public/creators/filter/?page=${page}`,
filters { filter }
); );
if (code === 200) { if (code === 200) {
return { data, pagination }; return { data, pagination };
@ -243,11 +243,11 @@ export const fetchPrivateCreators = createAsyncThunk(
async ({ page = 1 }, { getState, rejectWithValue, dispatch }) => { async ({ page = 1 }, { getState, rejectWithValue, dispatch }) => {
try { try {
const state = getState(); const state = getState();
const filters = state.filters; const { pricing, views_range, ...filter } = state.filters;
const { code, data, message, pagination } = await api.post( const { code, data, message, pagination } = await api.post(
`/daren_detail/private/pools/creators/filter/?page=${page}`, `/daren_detail/private/pools/creators/filter/?page=${page}`,
{ pool_id: 1, filters } { pool_id: 1, filter }
); );
if (code === 200) { if (code === 200) {
return { data, pagination }; return { data, pagination };
@ -262,9 +262,38 @@ export const fetchPrivateCreators = createAsyncThunk(
export const fetchCreatorDetail = createAsyncThunk( export const fetchCreatorDetail = createAsyncThunk(
'creators/fetchCreatorDetail', 'creators/fetchCreatorDetail',
async ({ creatorId }, { getState }) => { async ({ creatorId }, { getState, rejectWithValue }) => {
try {
const response = await api.get(`/daren_detail/creators/${creatorId}`); const response = await api.get(`/daren_detail/creators/${creatorId}`);
if (response.code === 200) {
return response; return response;
} else {
throw new Error(response.message);
}
} catch (error) {
return rejectWithValue(error.message);
}
}
);
export const addCreatorsToCampaign = createAsyncThunk(
'creators/addCreatorsToCampaign',
async ({ creatorIds, campaignId }, { getState, rejectWithValue }) => {
try {
const response = await api.post(`/daren_detail/campaigns/add/`, {
creator_ids: creatorIds,
campaign_id: campaignId,
});
if (response.code === 200) {
dispatch(setNotificationBarMessage({ message: 'Creators added to campaign successfully', type: 'success' }));
return response;
} else {
dispatch(setNotificationBarMessage({ message: response.message, type: 'error' }));
throw new Error(response.message);
}
} catch (error) {
return rejectWithValue(error.message);
}
} }
); );
@ -284,9 +313,9 @@ const creatorsSlice = createSlice({
}, },
selectAllCreators: (state, action) => { selectAllCreators: (state, action) => {
if (action.payload === 'database') { if (action.payload === 'database') {
state.selectedCreators = state.publicCreators.map((creator) => creator.id); state.selectedCreators = state.publicCreators.map((creator) => creator.creator_id);
} else { } else {
state.selectedCreators = state.privateCreators.map((creator) => creator.id); state.selectedCreators = state.privateCreators.map((creator) => creator.creator_id);
} }
}, },
clearCreatorSelection: (state) => { clearCreatorSelection: (state) => {

View File

@ -1,14 +1,14 @@
import { createSlice } from '@reduxjs/toolkit'; import { createSlice } from '@reduxjs/toolkit';
const initialState = { const initialState = {
category: ['Homes Supplies'], category: [],
e_commerce_level: ['L2', 'L3'], e_commerce_level: [],
exposure_level: [], exposure_level: [],
gmv_range: ['$5k - $25k', '$25k - $60k'], gmv_range: [],
views_range: [0, 100000], views_range: [0, 100000],
pricing: [0, 3000], pricing: [0, 3000],
sortBy: 'followers', // sortBy: 'followers',
sortDirection: 'desc', // sortDirection: 'desc',
platform: '', platform: '',
}; };
const filtersSlice = createSlice({ const filtersSlice = createSlice({

View File

@ -1,3 +1,5 @@
import { createSlice } from "@reduxjs/toolkit";
const initialState = { const initialState = {
message: '', message: '',
show: false, show: false,
@ -9,10 +11,10 @@ const notificationBarSlice = createSlice({
initialState, initialState,
reducers: { reducers: {
setNotificationBarMessage: (state, action) => { setNotificationBarMessage: (state, action) => {
state.message = action.payload; const { message, type } = action.payload;
}, state.message = message;
setNotificationBarShow: (state, action) => { state.type = type;
state.show = action.payload; state.show = true;
}, },
resetNotificationBar: (state) => { resetNotificationBar: (state) => {
state.message = ''; state.message = '';
@ -22,6 +24,6 @@ const notificationBarSlice = createSlice({
}, },
}); });
export const { setNotificationBarMessage, setNotificationBarShow, resetNotificationBar } = notificationBarSlice.actions; export const { setNotificationBarMessage, resetNotificationBar } = notificationBarSlice.actions;
export default notificationBarSlice.reducer; export default notificationBarSlice.reducer;