[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/custom-theme.scss';
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);
@ -12,7 +13,12 @@ ChartJS.register(ArcElement, Tooltip, Legend, BarElement, LinearScale, CategoryS
library.add(faTiktok, fas, faYoutube, faInstagram);
function App() {
return <Router />;
return (
<>
<Router />
<NotificationBar />
</>
);
}
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 { useDispatch } from 'react-redux';
import { useDispatch, useSelector } from 'react-redux';
import { resetNotificationBar } from '../store/slices/notificationBarSlice';
import { useEffect } from 'react';
export default function NotificationBar() {
const { message, type, show } = useSelector((state) => state.notificationBar);
const dispatch = useDispatch();
const setTimeOut = () => {
setTimeout(() => {
dispatch(resetNotificationBar());
}, 3000);
};
const handleClose = () => {
dispatch(resetNotificationBar());
};
useEffect(() => {
console.log(message);
setTimeOut();
}, [message]);
return (
show && (
<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'
style={{ zIndex: 999999, top: '15%' }}
>
{type === 'success' && <Check />}
{type === 'warning' && <CircleAlert />}
@ -17,5 +36,6 @@ export default function NotificationBar() {
<div className='flex-fill'>{message}</div>
<button type='button' className='btn-close flex-end' onClick={handleClose} aria-label='Close'></button>
</div>
)
);
}

View File

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

View File

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

View File

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

View File

@ -221,11 +221,11 @@ export const fetchCreators = createAsyncThunk(
async ({ page = 1 }, { getState, rejectWithValue }) => {
try {
const state = getState();
const filters = state.filters;
const { pricing, views_range, ...filter } = state.filters;
const { code, data, message, pagination } = await api.post(
`/daren_detail/public/creators/filter/?page=${page}`,
filters
{ filter }
);
if (code === 200) {
return { data, pagination };
@ -243,11 +243,11 @@ export const fetchPrivateCreators = createAsyncThunk(
async ({ page = 1 }, { getState, rejectWithValue, dispatch }) => {
try {
const state = getState();
const filters = state.filters;
const { pricing, views_range, ...filter } = state.filters;
const { code, data, message, pagination } = await api.post(
`/daren_detail/private/pools/creators/filter/?page=${page}`,
{ pool_id: 1, filters }
{ pool_id: 1, filter }
);
if (code === 200) {
return { data, pagination };
@ -262,9 +262,38 @@ export const fetchPrivateCreators = createAsyncThunk(
export const fetchCreatorDetail = createAsyncThunk(
'creators/fetchCreatorDetail',
async ({ creatorId }, { getState }) => {
async ({ creatorId }, { getState, rejectWithValue }) => {
try {
const response = await api.get(`/daren_detail/creators/${creatorId}`);
if (response.code === 200) {
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) => {
if (action.payload === 'database') {
state.selectedCreators = state.publicCreators.map((creator) => creator.id);
state.selectedCreators = state.publicCreators.map((creator) => creator.creator_id);
} else {
state.selectedCreators = state.privateCreators.map((creator) => creator.id);
state.selectedCreators = state.privateCreators.map((creator) => creator.creator_id);
}
},
clearCreatorSelection: (state) => {

View File

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

View File

@ -1,3 +1,5 @@
import { createSlice } from "@reduxjs/toolkit";
const initialState = {
message: '',
show: false,
@ -9,10 +11,10 @@ const notificationBarSlice = createSlice({
initialState,
reducers: {
setNotificationBarMessage: (state, action) => {
state.message = action.payload;
},
setNotificationBarShow: (state, action) => {
state.show = action.payload;
const { message, type } = action.payload;
state.message = message;
state.type = type;
state.show = true;
},
resetNotificationBar: (state) => {
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;