mirror of
https://github.com/Funkoala14/CreatorCenter_OOIN.git
synced 2025-06-07 21:48:14 +08:00
[dev]productdetail
This commit is contained in:
parent
855ea29b92
commit
a248d7dedf
@ -1,7 +1,16 @@
|
||||
import { useSelector } from "react-redux";
|
||||
import { useEffect } from 'react';
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { fetchProductDetail } from '../store/slices/productSlice';
|
||||
import { mockCreators } from '../store/slices/creatorsSlice';
|
||||
import { Accordion, Table } from 'react-bootstrap';
|
||||
|
||||
export default function ProductDetail() {
|
||||
const selectedProduct = useSelector((state) => state.brands.selectedProduct);
|
||||
const dispatch = useDispatch();
|
||||
|
||||
useEffect(() => {
|
||||
dispatch(fetchProductDetail(selectedProduct.id));
|
||||
}, [selectedProduct.id]);
|
||||
|
||||
return (
|
||||
<div className='product-details shadow-xs'>
|
||||
@ -21,7 +30,9 @@ export default function ProductDetail() {
|
||||
<div className='product-detail-item-label'>Available Samples</div>
|
||||
</div>
|
||||
<div className='product-detail-item'>
|
||||
<div className='product-detail-item-value'>{selectedProduct.sales_price_max} - {selectedProduct.sales_price_min}</div>
|
||||
<div className='product-detail-item-value'>
|
||||
{selectedProduct.sales_price_max} - {selectedProduct.sales_price_min}
|
||||
</div>
|
||||
<div className='product-detail-item-label'>Sales Price</div>
|
||||
</div>
|
||||
<div className='product-detail-item'>
|
||||
@ -53,3 +64,71 @@ export default function ProductDetail() {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export const CampaignsCollabCreators = () => {
|
||||
const mockData = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'SUNLINK 拍拍灯',
|
||||
creatorList: mockCreators,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'SUNLINK 拍拍灯2',
|
||||
creatorList: mockCreators,
|
||||
},
|
||||
];
|
||||
|
||||
if (mockData.length === 0) {
|
||||
return <div className='text-center'>No campaigns found</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Accordion className='campaigns-collab-creators-list' defaultActiveKey={mockData[0].id}>
|
||||
这个接口是用哪个?根据pid获取关联的活动-根据活动获取creatorList
|
||||
{mockData.map((item) => (
|
||||
<Accordion.Item eventKey={item.id} key={item.id} className='campaigns-collab-creators-item'>
|
||||
<Accordion.Header>{item.name}</Accordion.Header>
|
||||
<Accordion.Body>
|
||||
<Table responsive hover className='bg-white shadow-xs rounded overflow-hidden'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Creator</th>
|
||||
<th>Category</th>
|
||||
<th>Followers</th>
|
||||
<th>GMV Generated</th>
|
||||
<th>Views Generated</th>
|
||||
<th>Pricing</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{item?.creatorList.length > 0 &&
|
||||
item?.creatorList.map((creator) => (
|
||||
<tr key={creator.id}>
|
||||
<td>
|
||||
<div className='white-space-nowrap'>
|
||||
<img
|
||||
className='creator-avatar'
|
||||
src={creator.avatar}
|
||||
alt={creator.name}
|
||||
/>
|
||||
<span className='creator-name'>{creator.name}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>{creator.category}</td>
|
||||
<td>{creator.followers || '--'}</td>
|
||||
<td>{creator.gmv || '--'}</td>
|
||||
<td>{creator.views || '--'}</td>
|
||||
<td>{creator.pricing || '--'}</td>
|
||||
<td>{creator.status || '--'}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</Table>
|
||||
</Accordion.Body>
|
||||
</Accordion.Item>
|
||||
))}
|
||||
</Accordion>
|
||||
);
|
||||
};
|
||||
|
@ -14,7 +14,7 @@ import {
|
||||
createCampaignThunk,
|
||||
} from '../store/slices/brandsSlice';
|
||||
import SlidePanel from '../components/SlidePanel';
|
||||
import ProductDetail from '../components/ProductDetail';
|
||||
import ProductDetail, { CampaignsCollabCreators } from '../components/ProductDetail';
|
||||
import { CAMPAIGN_SERVICES, CREATOR_CATEGORIES, CREATOR_LEVELS, CREATOR_TYPES, GMV_RANGES } from '../lib/constant';
|
||||
import RangeSlider from '../components/RangeSlider';
|
||||
import SpinningComponent from '../components/Spinning';
|
||||
@ -147,7 +147,10 @@ export default function BrandsDetail() {
|
||||
title='Product Detail'
|
||||
size='xxl'
|
||||
>
|
||||
<div className='product-details-panel'>
|
||||
<ProductDetail />
|
||||
<CampaignsCollabCreators />
|
||||
</div>
|
||||
</SlidePanel>
|
||||
</>
|
||||
)}
|
||||
|
@ -1,10 +1,9 @@
|
||||
import { useDispatch, useSelector } from 'react-redux';
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { Accordion, Button, Card, Col, Form, Row, Table } from 'react-bootstrap';
|
||||
import { Button, Card, Col, Form, Row } from 'react-bootstrap';
|
||||
import { CloudUpload, Paperclip } from 'lucide-react';
|
||||
import { useDropzone } from 'react-dropzone';
|
||||
import { mockCreators } from '../store/slices/creatorsSlice';
|
||||
import ProductDetail from '../components/ProductDetail';
|
||||
import ProductDetail, { CampaignsCollabCreators } from '../components/ProductDetail';
|
||||
|
||||
export default function CampaignScript() {
|
||||
const dispatch = useDispatch();
|
||||
@ -139,73 +138,6 @@ const CollabInfo = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const CampaignsCollabCreators = () => {
|
||||
const mockData = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'SUNLINK 拍拍灯',
|
||||
creatorList: mockCreators,
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'SUNLINK 拍拍灯2',
|
||||
creatorList: mockCreators,
|
||||
},
|
||||
];
|
||||
|
||||
if (mockData.length === 0) {
|
||||
return <div className='text-center'>No campaigns found</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<Accordion className='campaigns-collab-creators-list' defaultActiveKey={mockData[0].id}>
|
||||
{mockData.map((item) => (
|
||||
<Accordion.Item eventKey={item.id} key={item.id} className='campaigns-collab-creators-item'>
|
||||
<Accordion.Header>{item.name}</Accordion.Header>
|
||||
<Accordion.Body>
|
||||
<Table responsive hover className='bg-white shadow-xs rounded overflow-hidden'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Creator</th>
|
||||
<th>Category</th>
|
||||
<th>Followers</th>
|
||||
<th>GMV Generated</th>
|
||||
<th>Views Generated</th>
|
||||
<th>Pricing</th>
|
||||
<th>Status</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{item?.creatorList.length > 0 &&
|
||||
item?.creatorList.map((creator) => (
|
||||
<tr key={creator.id}>
|
||||
<td>
|
||||
<div className='white-space-nowrap'>
|
||||
<img
|
||||
className='creator-avatar'
|
||||
src={creator.avatar}
|
||||
alt={creator.name}
|
||||
/>
|
||||
<span className='creator-name'>{creator.name}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>{creator.category}</td>
|
||||
<td>{creator.followers || '--'}</td>
|
||||
<td>{creator.gmv || '--'}</td>
|
||||
<td>{creator.views || '--'}</td>
|
||||
<td>{creator.pricing || '--'}</td>
|
||||
<td>{creator.status || '--'}</td>
|
||||
</tr>
|
||||
))}
|
||||
</tbody>
|
||||
</Table>
|
||||
</Accordion.Body>
|
||||
</Accordion.Item>
|
||||
))}
|
||||
</Accordion>
|
||||
);
|
||||
};
|
||||
|
||||
const FileUpload = () => {
|
||||
const [files, setFiles] = useState([]);
|
||||
|
||||
|
41
src/store/slices/chatSlice.js
Normal file
41
src/store/slices/chatSlice.js
Normal file
@ -0,0 +1,41 @@
|
||||
import { createSlice } from '@reduxjs/toolkit';
|
||||
|
||||
export const fetchChats = createAsyncThunk('chat/fetchChats', async (_, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await api.get(`/chat-history/search/`);
|
||||
if (response.code === 200) {
|
||||
return response.data;
|
||||
}
|
||||
throw new Error(response.message);
|
||||
} catch (error) {
|
||||
return rejectWithValue(error.message);
|
||||
}
|
||||
});
|
||||
|
||||
const initialState = {
|
||||
chats: [],
|
||||
currentChat: null,
|
||||
status: 'idle',
|
||||
error: null,
|
||||
};
|
||||
|
||||
const chatSlice = createSlice({
|
||||
name: 'chat',
|
||||
initialState,
|
||||
reducers: {},
|
||||
extraReducers: (builder) => {
|
||||
builder.addCase(fetchChats.pending, (state) => {
|
||||
state.status = 'loading';
|
||||
})
|
||||
builder.addCase(fetchChats.fulfilled, (state, action) => {
|
||||
state.status = 'succeeded';
|
||||
state.chats = action.payload;
|
||||
})
|
||||
builder.addCase(fetchChats.rejected, (state, action) => {
|
||||
state.status = 'failed';
|
||||
state.error = action.payload;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
export default chatSlice.reducer;
|
@ -1,4 +1,6 @@
|
||||
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
|
||||
import api from '@/services/api';
|
||||
import { setNotificationBarMessage } from './notificationBarSlice';
|
||||
|
||||
const mockCreators = [
|
||||
{
|
||||
@ -22,11 +24,38 @@ const mockCreators = [
|
||||
date: '2021-01-01',
|
||||
},
|
||||
];
|
||||
export const fetchDiscovery = createAsyncThunk('discovery/fetchDiscovery', async (search) => {
|
||||
// const response = await fetch('/api/discovery');
|
||||
// return response.json();
|
||||
return mockCreators;
|
||||
});
|
||||
export const fetchDiscovery = createAsyncThunk(
|
||||
'discovery/fetchDiscovery',
|
||||
async (searchParams, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await api.post('/creators/search/', searchParams);
|
||||
if (response.code === 200) {
|
||||
return response.data;
|
||||
}
|
||||
throw new Error(response.message);
|
||||
} catch (error) {
|
||||
dispatch(setNotificationBarMessage({ message: error.message, type: 'error' }));
|
||||
return rejectWithValue(error.message);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
export const fetchDiscoveryByMode = createAsyncThunk(
|
||||
'discovery/fetchDiscoveryByMode',
|
||||
async (params, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await api.post('/discovery/creators/search_tags/', params);
|
||||
if (response.code === 200) {
|
||||
return response.data;
|
||||
}
|
||||
throw new Error(response.message);
|
||||
} catch (error) {
|
||||
dispatch(setNotificationBarMessage({ message: error.message, type: 'error' }));
|
||||
return rejectWithValue(error.message);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const initialState = {
|
||||
creators: [],
|
||||
status: 'idle',
|
||||
@ -49,6 +78,17 @@ const discoverySlice = createSlice({
|
||||
.addCase(fetchDiscovery.rejected, (state, action) => {
|
||||
state.status = 'failed';
|
||||
state.error = action.error.message;
|
||||
})
|
||||
.addCase(fetchDiscoveryByMode.pending, (state) => {
|
||||
state.status = 'loading';
|
||||
})
|
||||
.addCase(fetchDiscoveryByMode.fulfilled, (state, action) => {
|
||||
state.status = 'succeeded';
|
||||
state.creators = action.payload;
|
||||
})
|
||||
.addCase(fetchDiscoveryByMode.rejected, (state, action) => {
|
||||
state.status = 'failed';
|
||||
state.error = action.error.message;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit';
|
||||
import { format, isToday, parseISO } from 'date-fns';
|
||||
import api from '@/services/api';
|
||||
|
||||
const mockTemplates = [
|
||||
{
|
||||
id: 1,
|
||||
@ -169,13 +171,16 @@ const chatDateFormat = (date) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchInboxList = createAsyncThunk('inbox/fetchInboxList', async () => {
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
const formattedInboxList = mockInboxList.map((item) => ({
|
||||
...item,
|
||||
date: chatDateFormat(item.date),
|
||||
}));
|
||||
return formattedInboxList;
|
||||
export const fetchInboxList = createAsyncThunk('inbox/fetchInboxList', async (_, { rejectWithValue }) => {
|
||||
try {
|
||||
const response = await api.get(`/chat-history/`);
|
||||
if (response.code === 200) {
|
||||
return response.data;
|
||||
}
|
||||
throw new Error(response.message);
|
||||
} catch (error) {
|
||||
return rejectWithValue(error.message);
|
||||
}
|
||||
});
|
||||
|
||||
export const fetchChatHistory = createAsyncThunk('inbox/fetchChatHistory', async (id) => {
|
||||
|
@ -29,10 +29,24 @@ export const addProductToCampaign = createAsyncThunk('products/addProductToCampa
|
||||
}
|
||||
});
|
||||
|
||||
export const fetchProductDetail = createAsyncThunk('products/fetchProductDetail', async (productId, { rejectWithValue, dispatch }) => {
|
||||
try {
|
||||
const response = await api.get(`/products/${productId}/`);
|
||||
if (response.code !== 200) {
|
||||
throw new Error(response.message);
|
||||
}
|
||||
return response.data;
|
||||
} catch (error) {
|
||||
dispatch(setNotificationBarMessage({ message: error.message, type: 'error' }));
|
||||
return rejectWithValue(error.message);
|
||||
}
|
||||
});
|
||||
|
||||
const initialState = {
|
||||
products: [],
|
||||
loading: false,
|
||||
error: null,
|
||||
productDetail: null,
|
||||
};
|
||||
|
||||
const productSlice = createSlice({
|
||||
@ -62,6 +76,17 @@ const productSlice = createSlice({
|
||||
state.error = action.payload;
|
||||
state.loading = false;
|
||||
})
|
||||
builder.addCase(fetchProductDetail.pending, (state) => {
|
||||
state.loading = true;
|
||||
})
|
||||
builder.addCase(fetchProductDetail.fulfilled, (state, action) => {
|
||||
state.productDetail = action.payload;
|
||||
state.loading = false;
|
||||
})
|
||||
builder.addCase(fetchProductDetail.rejected, (state, action) => {
|
||||
state.error = action.payload;
|
||||
state.loading = false;
|
||||
})
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -420,3 +420,9 @@
|
||||
gap: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.product-details-panel {
|
||||
display: flex;
|
||||
flex-flow: column nowrap;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user