mirror of
https://github.com/Funkoala14/CreatorCenter_OOIN.git
synced 2025-06-08 06:38:14 +08:00
Compare commits
3 Commits
2be18a979d
...
443c253014
Author | SHA1 | Date | |
---|---|---|---|
443c253014 | |||
4c22e12101 | |||
c518639845 |
@ -33,14 +33,14 @@ export default function BrandsList({ openBrandDetail }) {
|
|||||||
<Hash size={16} />
|
<Hash size={16} />
|
||||||
Collab.
|
Collab.
|
||||||
</span>
|
</span>
|
||||||
<span className='card-text-content'>{brand.collab}</span>
|
<span className='card-text-content'>{brand.collab_count}</span>
|
||||||
</Card.Text>
|
</Card.Text>
|
||||||
<Card.Text className=''>
|
<Card.Text className=''>
|
||||||
<span className='card-text-title'>
|
<span className='card-text-title'>
|
||||||
<Users size={16} />
|
<Users size={16} />
|
||||||
Creators
|
Creators
|
||||||
</span>
|
</span>
|
||||||
<span className='card-text-content'>{brand.creators}</span>
|
<span className='card-text-content'>{brand.creators_count}</span>
|
||||||
</Card.Text>
|
</Card.Text>
|
||||||
<Card.Text className=''>
|
<Card.Text className=''>
|
||||||
<span className='card-text-title'>
|
<span className='card-text-title'>
|
||||||
|
@ -3,14 +3,23 @@ import { useEffect } from 'react';
|
|||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
import { Link } from 'react-router-dom';
|
import { Link } from 'react-router-dom';
|
||||||
import { ChartNoAxesColumnIncreasing, CircleDollarSign, Edit, Eye, Folders, Hash, Layers, Tag, TrendingUp, UserRoundCheck } from 'lucide-react';
|
import { ChartNoAxesColumnIncreasing, CircleDollarSign, Edit, Eye, Folders, Hash, Layers, Tag, TrendingUp, UserRoundCheck } from 'lucide-react';
|
||||||
|
import Spinning from './Spinning';
|
||||||
|
|
||||||
export default function CampaignList() {
|
export default function CampaignList() {
|
||||||
const { selectedBrand } = useSelector((state) => state.brands);
|
const { selectedBrand ,status } = useSelector((state) => state.brands);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log(selectedBrand);
|
console.log(selectedBrand);
|
||||||
}, [selectedBrand]);
|
}, [selectedBrand]);
|
||||||
|
|
||||||
|
if (status === 'loading') {
|
||||||
|
return <Spinning />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedBrand?.campaigns?.length === 0) {
|
||||||
|
return <div>No campaigns found</div>;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='campaigns-list'>
|
<div className='campaigns-list'>
|
||||||
{selectedBrand?.campaigns?.length > 0 &&
|
{selectedBrand?.campaigns?.length > 0 &&
|
||||||
|
@ -54,7 +54,7 @@ export default function CreatorList({ path }) {
|
|||||||
// 处理全选/取消全选
|
// 处理全选/取消全选
|
||||||
const handleSelectAll = (e) => {
|
const handleSelectAll = (e) => {
|
||||||
if (e.target.checked) {
|
if (e.target.checked) {
|
||||||
dispatch(selectAllCreators());
|
dispatch(selectAllCreators('database'));
|
||||||
} else {
|
} else {
|
||||||
dispatch(clearCreatorSelection());
|
dispatch(clearCreatorSelection());
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState, useEffect } from 'react';
|
||||||
import { Link, useLocation } from 'react-router-dom';
|
import { Link, useLocation } from 'react-router-dom';
|
||||||
import { Nav, Accordion } from 'react-bootstrap';
|
import { Nav, Accordion, Button } from 'react-bootstrap';
|
||||||
import {
|
import {
|
||||||
Settings,
|
Settings,
|
||||||
ChevronDown,
|
ChevronDown,
|
||||||
@ -12,10 +12,12 @@ import {
|
|||||||
Heart,
|
Heart,
|
||||||
Send,
|
Send,
|
||||||
FileText,
|
FileText,
|
||||||
|
LogOut,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
import logo from '@/assets/logo.png';
|
import logo from '@/assets/logo.png';
|
||||||
import '@/styles/sidebar.scss';
|
import '@/styles/sidebar.scss';
|
||||||
|
import { useDispatch } from 'react-redux';
|
||||||
|
|
||||||
// Organized menu items
|
// Organized menu items
|
||||||
const menuItems = [
|
const menuItems = [
|
||||||
@ -233,6 +235,11 @@ export default function Sidebar() {
|
|||||||
}
|
}
|
||||||
})}
|
})}
|
||||||
</Nav>
|
</Nav>
|
||||||
|
<div className='sidebar-footer'>
|
||||||
|
<Button variant='outline-danger'>
|
||||||
|
<LogOut />
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,7 @@ export default function PrivateCreatorList({ path }) {
|
|||||||
// 处理全选/取消全选
|
// 处理全选/取消全选
|
||||||
const handleSelectAll = (e) => {
|
const handleSelectAll = (e) => {
|
||||||
if (e.target.checked) {
|
if (e.target.checked) {
|
||||||
dispatch(selectAllCreators());
|
dispatch(selectAllCreators('database'));
|
||||||
} else {
|
} else {
|
||||||
dispatch(clearCreatorSelection());
|
dispatch(clearCreatorSelection());
|
||||||
}
|
}
|
||||||
|
@ -4,23 +4,16 @@ import { useSelector, useDispatch } from 'react-redux';
|
|||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import '../styles/Products.scss';
|
import '../styles/Products.scss';
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
|
||||||
|
import Spinning from './Spinning';
|
||||||
|
|
||||||
export default function ProductsList({ onShowProductDetail }) {
|
export default function ProductsList({ onShowProductDetail }) {
|
||||||
const { brandId } = useParams();
|
const { brandId } = useParams();
|
||||||
const { brands } = useSelector((state) => state.brands);
|
const { selectedBrand } = useSelector((state) => state.brands);
|
||||||
const [products, setProducts] = useState([]);
|
const [products, setProducts] = useState([]);
|
||||||
const [selectedProducts, setSelectedProducts] = useState([]);
|
const [selectedProducts, setSelectedProducts] = useState([]);
|
||||||
const [sortField, setSortField] = useState(null);
|
const [sortField, setSortField] = useState(null);
|
||||||
const [sortDirection, setSortDirection] = useState('asc');
|
const [sortDirection, setSortDirection] = useState('asc');
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (brands.length > 0) {
|
|
||||||
const brand = brands.find((b) => b.id.toString() === brandId);
|
|
||||||
if (brand && brand.products) {
|
|
||||||
setProducts(brand.products);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}, [brands, brandId]);
|
|
||||||
|
|
||||||
const handleSort = (field) => {
|
const handleSort = (field) => {
|
||||||
if (sortField === field) {
|
if (sortField === field) {
|
||||||
@ -52,6 +45,11 @@ export default function ProductsList({ onShowProductDetail }) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (status === 'loading') {
|
||||||
|
return <Spinning />;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='products-list rounded shadow-xs'>
|
<div className='products-list rounded shadow-xs'>
|
||||||
<Table responsive hover className='bg-white rounded overflow-hidden m-0'>
|
<Table responsive hover className='bg-white rounded overflow-hidden m-0'>
|
||||||
@ -60,7 +58,7 @@ export default function ProductsList({ onShowProductDetail }) {
|
|||||||
<th className='selector' style={{ width: '40px' }}>
|
<th className='selector' style={{ width: '40px' }}>
|
||||||
<Form.Check
|
<Form.Check
|
||||||
type='checkbox'
|
type='checkbox'
|
||||||
checked={selectedProducts.length === products.length && products.length > 0}
|
checked={selectedProducts.length === selectedBrand?.products?.length && selectedBrand?.products?.length > 0}
|
||||||
onChange={handleSelectAll}
|
onChange={handleSelectAll}
|
||||||
/>
|
/>
|
||||||
</th>
|
</th>
|
||||||
@ -94,14 +92,14 @@ export default function ProductsList({ onShowProductDetail }) {
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
{products.length === 0 ? (
|
{selectedBrand?.products?.length === 0 ? (
|
||||||
<tr>
|
<tr>
|
||||||
<td colSpan='10' className='text-center py-4'>
|
<td colSpan='10' className='text-center py-4'>
|
||||||
No products found for this brand.
|
No products found for this brand.
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
) : (
|
) : (
|
||||||
products.map((product) => (
|
selectedBrand?.products?.map((product) => (
|
||||||
<tr key={product.id} className={selectedProducts.includes(product.id) ? 'selected' : ''}>
|
<tr key={product.id} className={selectedProducts.includes(product.id) ? 'selected' : ''}>
|
||||||
<td>
|
<td>
|
||||||
<Form.Check
|
<Form.Check
|
||||||
@ -117,19 +115,19 @@ export default function ProductsList({ onShowProductDetail }) {
|
|||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td className='text-center' >
|
<td className='text-center' >
|
||||||
<div>{product.commission}</div>
|
<div>{product.commission_rate}</div>
|
||||||
<div className='small text-muted'>Open collab. {product.openCollab}</div>
|
<div className='small text-muted'>Open collab. {product.open_collab}</div>
|
||||||
</td>
|
</td>
|
||||||
<td className='text-center'>{product.availableSamples}</td>
|
<td className='text-center'>{product.available_samples}</td>
|
||||||
<td className='text-center'>{product.price}</td>
|
<td className='text-center'>{product.sales_price_min} - {product.sales_price_max}</td>
|
||||||
<td className='text-center'>{product.stock}</td>
|
<td className='text-center'>{product.stock}</td>
|
||||||
<td className='text-center'>{product.sold}</td>
|
<td className='text-center'>{product.items_sold}</td>
|
||||||
<td className='text-center'>
|
<td className='text-center'>
|
||||||
<div>{product.rating}</div>
|
<div>{product.product_rating}</div>
|
||||||
<div className='small text-muted'>{product.reviews} Reviews</div>
|
<div className='small text-muted'>{product.reviews_count} Reviews</div>
|
||||||
</td>
|
</td>
|
||||||
<td className='text-center'>{product.collabCreators}</td>
|
<td className='text-center'>{product.collab_creators}</td>
|
||||||
<td className='text-center'>{product.tiktokShop && <FontAwesomeIcon icon='fa-brands fa-tiktok' />}</td>
|
<td className='text-center'>{product.tiktok_shop && <FontAwesomeIcon icon='fa-brands fa-tiktok' />}</td>
|
||||||
</tr>
|
</tr>
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
|
@ -6,7 +6,7 @@ import { useDispatch, useSelector } from 'react-redux';
|
|||||||
import { Link, useParams } from 'react-router-dom';
|
import { Link, useParams } from 'react-router-dom';
|
||||||
import CampaignList from '../components/CampaignList';
|
import CampaignList from '../components/CampaignList';
|
||||||
import ProductsList from '../components/ProductsList';
|
import ProductsList from '../components/ProductsList';
|
||||||
import { findBrandById } from '../store/slices/brandsSlice';
|
import { findBrandById, fetchBrandCampaigns, fetchBrandProducts } from '../store/slices/brandsSlice';
|
||||||
|
|
||||||
export default function BrandsDetail() {
|
export default function BrandsDetail() {
|
||||||
const { id } = useParams();
|
const { id } = useParams();
|
||||||
@ -16,9 +16,9 @@ export default function BrandsDetail() {
|
|||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (id) {
|
if (id) {
|
||||||
console.log(id);
|
|
||||||
|
|
||||||
dispatch(findBrandById(id));
|
dispatch(findBrandById(id));
|
||||||
|
dispatch(fetchBrandCampaigns(id));
|
||||||
|
dispatch(fetchBrandProducts(id));
|
||||||
}
|
}
|
||||||
}, [dispatch, id]);
|
}, [dispatch, id]);
|
||||||
|
|
||||||
@ -69,31 +69,31 @@ export default function BrandsDetail() {
|
|||||||
<Hash size={20} />
|
<Hash size={20} />
|
||||||
Collab.
|
Collab.
|
||||||
</div>
|
</div>
|
||||||
<div className='info-value'>{selectedBrand.collab}</div>
|
<div className='info-value'>{selectedBrand.collab_count}</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='info-item'>
|
<div className='info-item'>
|
||||||
<div className='info-name'>
|
<div className='info-name'>
|
||||||
<Users size={20} />
|
<Users size={20} />
|
||||||
Creators
|
Creators
|
||||||
</div>
|
</div>
|
||||||
<div className='info-value'>{selectedBrand.creators}</div>
|
<div className='info-value'>{selectedBrand.creators_count}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='brand-info-bottom'>
|
<div className='brand-info-bottom'>
|
||||||
<div className='info-item'>
|
<div className='info-item'>
|
||||||
<div className='info-value'>{selectedBrand.collab}</div>
|
<div className='info-value'>{selectedBrand.creators_count}</div>
|
||||||
<div className='info-name'>Total Collab. Creators</div>
|
<div className='info-name'>Total Collab. Creators</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='info-item'>
|
<div className='info-item'>
|
||||||
<div className='info-value'>{selectedBrand.collab}</div>
|
<div className='info-value'>{selectedBrand.total_gmv_achieved}</div>
|
||||||
<div className='info-name'>Total GMV Achieved</div>
|
<div className='info-name'>Total GMV Achieved</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='info-item'>
|
<div className='info-item'>
|
||||||
<div className='info-value'>{selectedBrand.collab}</div>
|
<div className='info-value'>{selectedBrand.total_views_achieved}</div>
|
||||||
<div className='info-name'>Total Views Achieved</div>
|
<div className='info-name'>Total Views Achieved</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='info-item'>
|
<div className='info-item'>
|
||||||
<div className='info-value'>{selectedBrand.collab}</div>
|
<div className='info-value'>{selectedBrand.shop_overall_rating}</div>
|
||||||
<div className='info-name'>Shop Overall Rating</div>
|
<div className='info-name'>Shop Overall Rating</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,17 +1,50 @@
|
|||||||
import '@/styles/Login.scss';
|
import '@/styles/Login.scss';
|
||||||
import { Button, Form, InputGroup } from 'react-bootstrap';
|
import { Button, Form, InputGroup } from 'react-bootstrap';
|
||||||
import { LockKeyhole, User } from 'lucide-react';
|
import { LockKeyhole, User } from 'lucide-react';
|
||||||
|
import { useDispatch, useSelector } from 'react-redux';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { loginThunk } from '../store/slices/authSlice';
|
||||||
|
|
||||||
export default function Login() {
|
export default function Login() {
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
email: '',
|
||||||
|
password: '',
|
||||||
|
});
|
||||||
|
const { isLoading, isAuthenticated } = useSelector((state) => state.auth);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const dispatch = useDispatch();
|
||||||
|
const handleChange = (e) => {
|
||||||
|
setFormData({ ...formData, [e.target.name]: e.target.value });
|
||||||
|
};
|
||||||
|
|
||||||
const handleSubmit = (e) => {
|
const handleSubmit = (e) => {
|
||||||
|
if (!handleValidate()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
console.log('Form submitted');
|
console.log('Form submitted');
|
||||||
|
dispatch(loginThunk(formData));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (isAuthenticated) {
|
||||||
|
navigate('/');
|
||||||
|
}
|
||||||
|
}, [isAuthenticated, navigate]);
|
||||||
|
|
||||||
|
const handleValidate = () => {
|
||||||
|
if (formData.email === '' || formData.password === '') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='login-container'>
|
<div className='login-container'>
|
||||||
<div className='title'>Creator Center</div>
|
<div className='title'>Creator Center</div>
|
||||||
<Form className='login-form' onSubmit={handleSubmit}>
|
<Form className='login-form' onSubmit={handleSubmit}>
|
||||||
<Form.Group>
|
{/* <Form.Group>
|
||||||
<Form.Label>Username</Form.Label>
|
<Form.Label>Username</Form.Label>
|
||||||
<InputGroup>
|
<InputGroup>
|
||||||
<InputGroup.Text>
|
<InputGroup.Text>
|
||||||
@ -19,6 +52,22 @@ export default function Login() {
|
|||||||
</InputGroup.Text>
|
</InputGroup.Text>
|
||||||
<Form.Control type='text' placeholder='Enter username' />
|
<Form.Control type='text' placeholder='Enter username' />
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
|
</Form.Group> */}
|
||||||
|
<Form.Group>
|
||||||
|
<Form.Label>Email</Form.Label>
|
||||||
|
<InputGroup>
|
||||||
|
<InputGroup.Text>
|
||||||
|
<User />
|
||||||
|
</InputGroup.Text>
|
||||||
|
<Form.Control
|
||||||
|
required
|
||||||
|
type='email'
|
||||||
|
placeholder='Enter email'
|
||||||
|
name='email'
|
||||||
|
value={formData.email}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
|
</InputGroup>
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
<Form.Group>
|
<Form.Group>
|
||||||
<Form.Label>Password</Form.Label>
|
<Form.Label>Password</Form.Label>
|
||||||
@ -26,10 +75,19 @@ export default function Login() {
|
|||||||
<InputGroup.Text>
|
<InputGroup.Text>
|
||||||
<LockKeyhole />
|
<LockKeyhole />
|
||||||
</InputGroup.Text>
|
</InputGroup.Text>
|
||||||
<Form.Control type='password' placeholder='Enter password' />
|
<Form.Control
|
||||||
|
required
|
||||||
|
type='password'
|
||||||
|
placeholder='Enter password'
|
||||||
|
name='password'
|
||||||
|
value={formData.password}
|
||||||
|
onChange={handleChange}
|
||||||
|
/>
|
||||||
</InputGroup>
|
</InputGroup>
|
||||||
</Form.Group>
|
</Form.Group>
|
||||||
<Button type='submit'>Sign In</Button>
|
<Button type='submit' loading={isLoading} disabled={!handleValidate()}>
|
||||||
|
Sign In
|
||||||
|
</Button>
|
||||||
</Form>
|
</Form>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -1,22 +1,23 @@
|
|||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
import store, { dispatch } from '@/store';
|
||||||
|
import { clearUser } from '../store/slices/authSlice';
|
||||||
const api = axios.create({
|
const api = axios.create({
|
||||||
baseURL: '/api',
|
baseURL: '/api',
|
||||||
withCredentials: true, // Include cookies if needed
|
withCredentials: true, // Include cookies if needed
|
||||||
});
|
});
|
||||||
|
|
||||||
api.interceptors.request.use(
|
api.interceptors.request.use(
|
||||||
(config) => {
|
(config) => {
|
||||||
const token = sessionStorage.getItem('token') || '03d9163150a8bfbbee2a33c4444237f337a35278';
|
const token = sessionStorage.getItem('token') || '';
|
||||||
if (token) {
|
if (token) {
|
||||||
config.headers.Authorization = `Token ${token}`;
|
config.headers.Authorization = `Token ${token}`;
|
||||||
}
|
}
|
||||||
return config;
|
return config;
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
console.error('Request error:', error);
|
console.error('Request error:', error);
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
api.interceptors.response.use(
|
api.interceptors.response.use(
|
||||||
@ -24,6 +25,11 @@ api.interceptors.response.use(
|
|||||||
return response;
|
return response;
|
||||||
},
|
},
|
||||||
(error) => {
|
(error) => {
|
||||||
|
if (error.response.status === 401) {
|
||||||
|
sessionStorage.removeItem('token');
|
||||||
|
window.location.href = '/login';
|
||||||
|
dispatch(clearUser());
|
||||||
|
}
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
@ -46,16 +52,15 @@ const put = async (url, data) => {
|
|||||||
const del = async (url) => {
|
const del = async (url) => {
|
||||||
const response = await api.delete(url);
|
const response = await api.delete(url);
|
||||||
return response.data;
|
return response.data;
|
||||||
};
|
|
||||||
|
|
||||||
const upload = async (url, data) => {
|
|
||||||
const response = await api.post(url, data, {
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'multipart/form-data',
|
|
||||||
},
|
|
||||||
});
|
|
||||||
return response.data;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const upload = async (url, data) => {
|
||||||
|
const response = await api.post(url, data, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
};
|
||||||
|
|
||||||
export default { get, post, put, del, upload };
|
export default { get, post, put, del, upload };
|
||||||
|
@ -33,5 +33,5 @@ const store = configureStore({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const persistor = persistStore(store);
|
export const persistor = persistStore(store);
|
||||||
|
|
||||||
export default store;
|
export default store;
|
||||||
|
export const dispatch = store.dispatch;
|
||||||
|
@ -1,9 +1,24 @@
|
|||||||
import { createSlice } from "@reduxjs/toolkit";
|
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
|
||||||
|
import api from '@/services/api';
|
||||||
|
|
||||||
|
export const loginThunk = createAsyncThunk('auth/login', async (credentials, { rejectWithValue }) => {
|
||||||
|
try {
|
||||||
|
const response = await api.post('/user/login/', credentials);
|
||||||
|
if (response.code === 200) {
|
||||||
|
sessionStorage.setItem('token', response.data.token);
|
||||||
|
return response.data;
|
||||||
|
} else {
|
||||||
|
return rejectWithValue(response.message);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return rejectWithValue(error.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
user: null,
|
user: null,
|
||||||
token: null,
|
|
||||||
isAuthenticated: false,
|
isAuthenticated: false,
|
||||||
|
isLoading: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
const authSlice = createSlice({
|
const authSlice = createSlice({
|
||||||
@ -13,8 +28,26 @@ const authSlice = createSlice({
|
|||||||
setUser: (state, action) => {
|
setUser: (state, action) => {
|
||||||
state.user = action.payload;
|
state.user = action.payload;
|
||||||
},
|
},
|
||||||
|
clearUser: (state) => {
|
||||||
|
state.user = null;
|
||||||
|
state.isAuthenticated = false;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
extraReducers: (builder) => {
|
||||||
|
builder.addCase(loginThunk.pending, (state, action) => {
|
||||||
|
state.isLoading = true;
|
||||||
|
});
|
||||||
|
builder.addCase(loginThunk.fulfilled, (state, action) => {
|
||||||
|
state.user = action.payload;
|
||||||
|
state.isAuthenticated = true;
|
||||||
|
state.isLoading = false;
|
||||||
|
});
|
||||||
|
builder.addCase(loginThunk.rejected, (state, action) => {
|
||||||
|
state.isAuthenticated = false;
|
||||||
|
state.isLoading = false;
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
export const { setUser } = authSlice.actions;
|
export const { setUser, clearUser } = authSlice.actions;
|
||||||
export default authSlice.reducer;
|
export default authSlice.reducer;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
|
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
|
||||||
|
import api from '@/services/api';
|
||||||
const mockProducts = [
|
const mockProducts = [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
@ -108,10 +108,19 @@ const mockBrands = [
|
|||||||
|
|
||||||
export const fetchBrands = createAsyncThunk('brands/fetchBrands', async () => {
|
export const fetchBrands = createAsyncThunk('brands/fetchBrands', async () => {
|
||||||
// const response = await fetch('https://api.example.com/brands');
|
// const response = await fetch('https://api.example.com/brands');
|
||||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
const response = await api.get('/brands/');
|
||||||
console.log('fetchBrands');
|
return response.data;
|
||||||
|
});
|
||||||
|
|
||||||
return mockBrands;
|
export const fetchBrandCampaigns = createAsyncThunk('brands/fetchBrandCampaigns', async (brandId) => {
|
||||||
|
const response = await api.get(`/brands/${brandId}/campaigns/`);
|
||||||
|
return response.data;
|
||||||
|
});
|
||||||
|
|
||||||
|
export const fetchBrandProducts = createAsyncThunk('brands/fetchBrandProducts', async (brandId) => {
|
||||||
|
const response = await api.get(`/brands/${brandId}/products/`);
|
||||||
|
console.log(response);
|
||||||
|
return response.data;
|
||||||
});
|
});
|
||||||
|
|
||||||
const initialState = {
|
const initialState = {
|
||||||
@ -161,6 +170,28 @@ const brandsSlice = createSlice({
|
|||||||
.addCase(fetchBrands.rejected, (state, action) => {
|
.addCase(fetchBrands.rejected, (state, action) => {
|
||||||
state.status = 'failed';
|
state.status = 'failed';
|
||||||
state.error = action.error.message;
|
state.error = action.error.message;
|
||||||
|
})
|
||||||
|
.addCase(fetchBrandCampaigns.pending, (state) => {
|
||||||
|
state.status = 'loading';
|
||||||
|
})
|
||||||
|
.addCase(fetchBrandCampaigns.fulfilled, (state, action) => {
|
||||||
|
state.status = 'succeeded';
|
||||||
|
state.selectedBrand.campaigns = action.payload;
|
||||||
|
})
|
||||||
|
.addCase(fetchBrandCampaigns.rejected, (state, action) => {
|
||||||
|
state.status = 'failed';
|
||||||
|
state.error = action.error.message;
|
||||||
|
})
|
||||||
|
.addCase(fetchBrandProducts.pending, (state) => {
|
||||||
|
state.status = 'loading';
|
||||||
|
})
|
||||||
|
.addCase(fetchBrandProducts.fulfilled, (state, action) => {
|
||||||
|
state.status = 'succeeded';
|
||||||
|
state.selectedBrand.products = action.payload;
|
||||||
|
})
|
||||||
|
.addCase(fetchBrandProducts.rejected, (state, action) => {
|
||||||
|
state.status = 'failed';
|
||||||
|
state.error = action.error.message;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
|
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit';
|
||||||
import api from '../../services/api';
|
import api from '@/services/api';
|
||||||
|
|
||||||
const mockVideos = [
|
const mockVideos = [
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
@ -220,6 +221,7 @@ export const fetchCreators = createAsyncThunk('creators/fetchCreators', async ({
|
|||||||
const filters = state.filters;
|
const filters = state.filters;
|
||||||
|
|
||||||
const response = await api.get(`/daren_detail/public/creators`, { params: { page } });
|
const response = await api.get(`/daren_detail/public/creators`, { params: { page } });
|
||||||
|
console.log(response);
|
||||||
return response;
|
return response;
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -257,8 +259,12 @@ const creatorsSlice = createSlice({
|
|||||||
state.selectedCreators.push(creatorId);
|
state.selectedCreators.push(creatorId);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
selectAllCreators: (state) => {
|
selectAllCreators: (state, action) => {
|
||||||
state.selectedCreators = state.creators.map((creator) => creator.id);
|
if (action.payload === 'database') {
|
||||||
|
state.selectedCreators = state.publicCreators.map((creator) => creator.id);
|
||||||
|
} else {
|
||||||
|
state.selectedCreators = state.privateCreators.map((creator) => creator.id);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
clearCreatorSelection: (state) => {
|
clearCreatorSelection: (state) => {
|
||||||
state.selectedCreators = [];
|
state.selectedCreators = [];
|
||||||
@ -277,7 +283,7 @@ const creatorsSlice = createSlice({
|
|||||||
extraReducers: (builder) => {
|
extraReducers: (builder) => {
|
||||||
builder
|
builder
|
||||||
.addCase(fetchCreators.pending, (state) => {
|
.addCase(fetchCreators.pending, (state) => {
|
||||||
if (state.creators.length === 0) {
|
if (state.publicCreators.length === 0) {
|
||||||
state.status = 'loading';
|
state.status = 'loading';
|
||||||
} else {
|
} else {
|
||||||
state.isLoadingMore = true;
|
state.isLoadingMore = true;
|
||||||
@ -287,7 +293,6 @@ const creatorsSlice = createSlice({
|
|||||||
state.status = 'succeeded';
|
state.status = 'succeeded';
|
||||||
state.isLoadingMore = false;
|
state.isLoadingMore = false;
|
||||||
const { data, pagination } = action.payload;
|
const { data, pagination } = action.payload;
|
||||||
|
|
||||||
if (pagination.current_page === 1) {
|
if (pagination.current_page === 1) {
|
||||||
state.publicCreators = data;
|
state.publicCreators = data;
|
||||||
} else {
|
} else {
|
||||||
@ -302,7 +307,7 @@ const creatorsSlice = createSlice({
|
|||||||
state.error = action.error.message;
|
state.error = action.error.message;
|
||||||
})
|
})
|
||||||
.addCase(fetchPrivateCreators.pending, (state) => {
|
.addCase(fetchPrivateCreators.pending, (state) => {
|
||||||
if (state.creators?.length === 0) {
|
if (state.privateCreators?.length === 0) {
|
||||||
state.status = 'loading';
|
state.status = 'loading';
|
||||||
} else {
|
} else {
|
||||||
state.isLoadingMore = true;
|
state.isLoadingMore = true;
|
||||||
@ -332,7 +337,7 @@ const creatorsSlice = createSlice({
|
|||||||
})
|
})
|
||||||
.addCase(fetchCreatorDetail.fulfilled, (state, action) => {
|
.addCase(fetchCreatorDetail.fulfilled, (state, action) => {
|
||||||
state.status = 'succeeded';
|
state.status = 'succeeded';
|
||||||
const { data, pagination } = action.payload;
|
const { data } = action.payload;
|
||||||
state.selectedCreator = data;
|
state.selectedCreator = data;
|
||||||
})
|
})
|
||||||
.addCase(fetchCreatorDetail.rejected, (state, action) => {
|
.addCase(fetchCreatorDetail.rejected, (state, action) => {
|
||||||
|
@ -8,7 +8,8 @@
|
|||||||
gap: 1rem;
|
gap: 1rem;
|
||||||
|
|
||||||
.brand-card {
|
.brand-card {
|
||||||
width: 325px;
|
flex: 1;
|
||||||
|
min-width: 325px;
|
||||||
padding: 1.5rem;
|
padding: 1.5rem;
|
||||||
border-radius: 0.4rem;
|
border-radius: 0.4rem;
|
||||||
background-color: white;
|
background-color: white;
|
||||||
|
@ -31,6 +31,7 @@
|
|||||||
.btn {
|
.btn {
|
||||||
display: inline-flex !important;
|
display: inline-flex !important;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
}
|
}
|
||||||
.back-button {
|
.back-button {
|
||||||
width: max-content;
|
width: max-content;
|
||||||
|
Loading…
Reference in New Issue
Block a user