mirror of
https://github.com/Funkoala14/CreatorCenter_OOIN.git
synced 2025-06-07 21:48:14 +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/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;
|
||||
|
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 { 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>
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -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'>
|
||||
|
@ -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>
|
||||
);
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
@ -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) => {
|
||||
|
@ -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({
|
||||
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user