mirror of
https://github.com/Funkoala14/CreatorCenter_OOIN.git
synced 2025-06-08 05:19:43 +08:00
[dev]addtocampaign
This commit is contained in:
parent
8dd91b75c8
commit
dba045e0fe
@ -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;
|
||||||
|
87
src/components/AddToCampaign.jsx
Normal file
87
src/components/AddToCampaign.jsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
@ -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>
|
||||||
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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'>
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
|
@ -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;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -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) => {
|
||||||
|
@ -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({
|
||||||
|
@ -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;
|
||||||
|
Loading…
Reference in New Issue
Block a user