diff --git a/src/components/ProductDetail.jsx b/src/components/ProductDetail.jsx index e9d05b5..c966e94 100644 --- a/src/components/ProductDetail.jsx +++ b/src/components/ProductDetail.jsx @@ -1,8 +1,17 @@ -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 (
@@ -21,7 +30,9 @@ export default function ProductDetail() {
Available Samples
-
{selectedProduct.sales_price_max} - {selectedProduct.sales_price_min}
+
+ {selectedProduct.sales_price_max} - {selectedProduct.sales_price_min} +
Sales Price
@@ -53,3 +64,71 @@ export default function ProductDetail() {
); } + +export const CampaignsCollabCreators = () => { + const mockData = [ + { + id: 1, + name: 'SUNLINK 拍拍灯', + creatorList: mockCreators, + }, + { + id: 2, + name: 'SUNLINK 拍拍灯2', + creatorList: mockCreators, + }, + ]; + + if (mockData.length === 0) { + return
No campaigns found
; + } + + return ( + + 这个接口是用哪个?根据pid获取关联的活动-根据活动获取creatorList + {mockData.map((item) => ( + + {item.name} + + + + + + + + + + + + + + + {item?.creatorList.length > 0 && + item?.creatorList.map((creator) => ( + + + + + + + + + + ))} + +
CreatorCategoryFollowersGMV GeneratedViews GeneratedPricingStatus
+
+ {creator.name} + {creator.name} +
+
{creator.category}{creator.followers || '--'}{creator.gmv || '--'}{creator.views || '--'}{creator.pricing || '--'}{creator.status || '--'}
+
+
+ ))} +
+ ); +}; diff --git a/src/pages/BrandsDetail.jsx b/src/pages/BrandsDetail.jsx index 8af5b86..caab28d 100644 --- a/src/pages/BrandsDetail.jsx +++ b/src/pages/BrandsDetail.jsx @@ -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' > - +
+ + +
)} @@ -382,7 +385,7 @@ export function AddProductModal({ show, onHide }) { const addProduct = () => { const newIndex = Object.keys(productIds).length; console.log(newIndex); - + setProductIds((prev) => ({ ...prev, [newIndex]: '', diff --git a/src/pages/CampaignScript.jsx b/src/pages/CampaignScript.jsx index b70b597..527ac87 100644 --- a/src/pages/CampaignScript.jsx +++ b/src/pages/CampaignScript.jsx @@ -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
No campaigns found
; - } - - return ( - - {mockData.map((item) => ( - - {item.name} - - - - - - - - - - - - - - - {item?.creatorList.length > 0 && - item?.creatorList.map((creator) => ( - - - - - - - - - - ))} - -
CreatorCategoryFollowersGMV GeneratedViews GeneratedPricingStatus
-
- {creator.name} - {creator.name} -
-
{creator.category}{creator.followers || '--'}{creator.gmv || '--'}{creator.views || '--'}{creator.pricing || '--'}{creator.status || '--'}
-
-
- ))} -
- ); -}; - const FileUpload = () => { const [files, setFiles] = useState([]); diff --git a/src/store/slices/chatSlice.js b/src/store/slices/chatSlice.js new file mode 100644 index 0000000..63dbdfb --- /dev/null +++ b/src/store/slices/chatSlice.js @@ -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; diff --git a/src/store/slices/discoverySlice.js b/src/store/slices/discoverySlice.js index 9258733..4282b75 100644 --- a/src/store/slices/discoverySlice.js +++ b/src/store/slices/discoverySlice.js @@ -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; }); }, }); diff --git a/src/store/slices/inboxSlice.js b/src/store/slices/inboxSlice.js index 3f4f10e..73e3977 100644 --- a/src/store/slices/inboxSlice.js +++ b/src/store/slices/inboxSlice.js @@ -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) => { diff --git a/src/store/slices/productSlice.js b/src/store/slices/productSlice.js index fa4b116..2b47c2e 100644 --- a/src/store/slices/productSlice.js +++ b/src/store/slices/productSlice.js @@ -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; + }) }, }); diff --git a/src/styles/Brands.scss b/src/styles/Brands.scss index 798c9b6..d81c1d7 100644 --- a/src/styles/Brands.scss +++ b/src/styles/Brands.scss @@ -420,3 +420,9 @@ gap: 1rem; } } + +.product-details-panel { + display: flex; + flex-flow: column nowrap; + gap: 1rem; +}