From 7549e0f47b6bae1e609c303eb8f3a20c7833f549 Mon Sep 17 00:00:00 2001 From: susie-laptop Date: Fri, 23 May 2025 21:23:39 -0400 Subject: [PATCH] [dev]campaign&brand detail --- src/components/CampaignInfo.jsx | 14 ++-- src/components/CampaignList.jsx | 19 ++--- src/pages/Brands.jsx | 54 +++++++++++--- src/pages/BrandsDetail.jsx | 6 +- src/pages/CampaignDetail.jsx | 13 +++- src/store/slices/brandsSlice.js | 124 ++++++++++++++++++++++++++++---- src/styles/custom-theme.scss | 11 +++ 7 files changed, 195 insertions(+), 46 deletions(-) diff --git a/src/components/CampaignInfo.jsx b/src/components/CampaignInfo.jsx index 6163ccf..3394c29 100644 --- a/src/components/CampaignInfo.jsx +++ b/src/components/CampaignInfo.jsx @@ -27,8 +27,9 @@ export default function CampaignInfo() { Category
- {selectedCampaign?.category?.length > 0 && - selectedCampaign.category.map((cat,index) => {cat})} + {selectedCampaign?.creator_category || '--'} + {/* {selectedCampaign?.category?.length > 0 && + selectedCampaign.category.map((cat,index) => {cat})} */}
@@ -57,7 +58,7 @@ export default function CampaignInfo() { Pricing
-
{selectedCampaign?.pricing || '--'}
+
{selectedCampaign?.budget || '--'}
@@ -65,10 +66,11 @@ export default function CampaignInfo() { Creator Level
- {selectedCampaign?.creator_level?.length > 0 && + {selectedCampaign?.creator_level || '--'} + {/* {selectedCampaign?.creator_level?.length > 0 && selectedCampaign.creator_level.map((level,index) => ( {level} - ))} + ))} */}
@@ -83,7 +85,7 @@ export default function CampaignInfo() { Creators
-
{selectedCampaign?.creators || '--'}
+
{selectedCampaign?.creators_count || '--'}
diff --git a/src/components/CampaignList.jsx b/src/components/CampaignList.jsx index bb98a2b..321c05b 100644 --- a/src/components/CampaignList.jsx +++ b/src/components/CampaignList.jsx @@ -1,17 +1,18 @@ import React from 'react'; import { useEffect } from 'react'; import { useSelector } from 'react-redux'; -import { Link } from 'react-router-dom'; +import { Link, useParams } from 'react-router-dom'; import { ChartNoAxesColumnIncreasing, CircleDollarSign, Edit, Eye, Folders, Hash, Layers, Tag, TrendingUp, UserRoundCheck } from 'lucide-react'; import Spinning from './Spinning'; export default function CampaignList() { const { selectedBrand ,status } = useSelector((state) => state.brands); - useEffect(() => { console.log(selectedBrand); }, [selectedBrand]); + + if (status === 'loading') { return ; } @@ -34,26 +35,28 @@ export default function CampaignList() {
Creator Type
-
{campaign.creatorType}
+
{campaign.creator_type}
Creators
-
{campaign.creators}
+
{campaign.creators_count}
Creator Level
- {campaign.creator_level && + {campaign.creator_level} + {/* {campaign.creator_level && campaign.creator_level.map((level) => ( {level} - ))} + ))} */}
Category
- {campaign.category && - campaign.category.map((cat) => {cat})} + {campaign.creator_category} + {/* {campaign.category && + campaign.category.map((cat) => {cat})} */}
diff --git a/src/pages/Brands.jsx b/src/pages/Brands.jsx index 8cc503f..2d7314a 100644 --- a/src/pages/Brands.jsx +++ b/src/pages/Brands.jsx @@ -5,8 +5,9 @@ import { Button, Modal, Form } from 'react-bootstrap'; import '../styles/Brands.scss'; import { useNavigate } from 'react-router-dom'; import { Plus } from 'lucide-react'; -import { useDispatch } from 'react-redux'; -import { selectBrand } from '../store/slices/brandsSlice'; +import { useDispatch, useSelector } from 'react-redux'; +import { createBrandThunk, fetchBrands, selectBrand } from '../store/slices/brandsSlice'; +import SpinningComponent from '../components/Spinning'; export default function Brands() { const navigate = useNavigate(); @@ -43,16 +44,35 @@ function AddBrandModal({ show, onHide }) { const [brandName, setBrandName] = useState(''); const [brandSource, setBrandSource] = useState(''); const [campaignId, setCampaignId] = useState(''); + const [validated, setValidated] = useState(false); + const dispatch = useDispatch(); + const { status, error } = useSelector((state) => state.brands); const sourceOptions = [ - { value: '1', label: 'Third Party Agency' }, - { value: '2', label: 'Official Event' }, - { value: '3', label: 'Social Media' }, + { value: '1', label: 'TKS Official' }, + { value: '2', label: 'Third Party Agency' }, + { value: '3', label: 'Official Event' }, + { value: '4', label: 'Social Media' }, ]; - const handleCreateBrand = () => { - console.log(brandName, brandSource, campaignId); - handleCancel(); + const handleSubmit = async (e) => { + const form = document.getElementById('brandForm'); + if (form.checkValidity() === false) { + e.preventDefault(); + e.stopPropagation(); + setValidated(true); + return; + } + const brand = { + name: brandName, + source: brandSource, + campaign_id: campaignId, + }; + await dispatch(createBrandThunk(brand)).unwrap(); + if (status === 'succeeded') { + dispatch(fetchBrands()); + handleCancel(); + } }; const handleCancel = () => { @@ -62,17 +82,28 @@ function AddBrandModal({ show, onHide }) { setCampaignId(''); }; + if (status === 'loading') { + return ; + } + return ( Add Brand -
+ BrandName setBrandName(e.target.value)} @@ -80,7 +111,7 @@ function AddBrandModal({ show, onHide }) { Source - setBrandSource(e.target.value)}> + setBrandSource(e.target.value)} required> {sourceOptions.map((option) => (
diff --git a/src/pages/BrandsDetail.jsx b/src/pages/BrandsDetail.jsx index 4333a98..79c25a5 100644 --- a/src/pages/BrandsDetail.jsx +++ b/src/pages/BrandsDetail.jsx @@ -6,7 +6,7 @@ import { useDispatch, useSelector } from 'react-redux'; import { Link, useParams } from 'react-router-dom'; import CampaignList from '../components/CampaignList'; import ProductsList from '../components/ProductsList'; -import { findBrandById, fetchBrandCampaigns, fetchBrandProducts } from '../store/slices/brandsSlice'; +import { fetchBrandDetail, fetchBrandCampaigns, fetchBrandProducts } from '../store/slices/brandsSlice'; export default function BrandsDetail() { const { id } = useParams(); @@ -16,7 +16,7 @@ export default function BrandsDetail() { useEffect(() => { if (id) { - dispatch(findBrandById(id)); + dispatch(fetchBrandDetail(id)); dispatch(fetchBrandCampaigns(id)); dispatch(fetchBrandProducts(id)); } @@ -114,7 +114,7 @@ export default function BrandsDetail() { {activeTab === 'campaigns' && } - {activeTab === 'products' && } + {activeTab === 'products' && {}} />} ) ); diff --git a/src/pages/CampaignDetail.jsx b/src/pages/CampaignDetail.jsx index 328e5ed..e8a8654 100644 --- a/src/pages/CampaignDetail.jsx +++ b/src/pages/CampaignDetail.jsx @@ -3,7 +3,7 @@ import { Link, useParams } from 'react-router-dom'; import SearchBar from '../components/SearchBar'; import { Button, Form, Modal } from 'react-bootstrap'; import { useSelector, useDispatch } from 'react-redux'; -import { fetchBrands, findCampaignById, findProductById } from '../store/slices/brandsSlice'; +import { fetchBrands, fetchBrandDetail, findProductById, fetchCampaignDetail } from '../store/slices/brandsSlice'; import CampaignInfo from '../components/CampaignInfo'; import { ChevronRight, Send, Plus } from 'lucide-react'; import ProductsList from '../components/ProductsList'; @@ -26,7 +26,12 @@ export default function CampaignDetail() { useEffect(() => { if (brandId && campaignId) { - dispatch(findCampaignById({ brandId, campaignId })); + if (!selectedBrand) { + dispatch(fetchBrandDetail(brandId)); + dispatch(fetchCampaignDetail(campaignId)); + } else { + dispatch(fetchCampaignDetail(campaignId)); + } } }, [brandId, campaignId]); @@ -151,7 +156,9 @@ export default function CampaignDetail() { function AddProductModal({ show, onHide }) { return ( - Add Product + + Add Product + diff --git a/src/store/slices/brandsSlice.js b/src/store/slices/brandsSlice.js index 311a6f1..3ff7244 100644 --- a/src/store/slices/brandsSlice.js +++ b/src/store/slices/brandsSlice.js @@ -106,27 +106,89 @@ const mockBrands = [ }, ]; -export const fetchBrands = createAsyncThunk('brands/fetchBrands', async () => { - // const response = await fetch('https://api.example.com/brands'); - const response = await api.get('/brands/'); - return response.data; +export const fetchBrands = createAsyncThunk('brands/fetchBrands', async (_, { rejectWithValue, dispatch }) => { + try { + const response = await api.get('/brands/'); + 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); + } }); -export const fetchBrandCampaigns = createAsyncThunk('brands/fetchBrandCampaigns', async (brandId) => { - const response = await api.get(`/brands/${brandId}/campaigns/`); - return response.data; +export const fetchBrandDetail = createAsyncThunk('brands/fetchBrandDetail', async (brandId, { rejectWithValue }) => { + try { + const response = await api.get(`/brands/${brandId}/`); + if (response.code === 200) { + return response.data; + } + throw new Error(response.message); + } catch (error) { + return rejectWithValue(error.message); + } +}); +export const fetchBrandCampaigns = createAsyncThunk('brands/fetchBrandCampaigns', async (brandId, { rejectWithValue }) => { + try { + const response = await api.get(`/brands/${brandId}/campaigns/`); + console.log(response); + if (response.code === 200) { + return response.data; + } + throw new Error(response.message); + } catch (error) { + return rejectWithValue(error.message); + } }); -export const fetchBrandProducts = createAsyncThunk('brands/fetchBrandProducts', async (brandId) => { - const response = await api.get(`/brands/${brandId}/products/`); - console.log(response); - return response.data; +export const fetchCampaignDetail = createAsyncThunk('brands/fetchCampaignDetail', async (campaignId, { rejectWithValue }) => { + try { + const response = await api.get(`/campaigns/${campaignId}/`); + if (response.code === 200) { + return response.data; + } + throw new Error(response.message); + } catch (error) { + return rejectWithValue(error.message); + } }); -export const fetchCampaigns = createAsyncThunk('brands/fetchCampaigns', async () => { - const response = await api.get('/campaigns/'); - console.log(response); - return response.data; +export const fetchBrandProducts = createAsyncThunk('brands/fetchBrandProducts', async (brandId, { rejectWithValue }) => { + try { + const response = await api.get(`/brands/${brandId}/products/`); + if (response.code !== 200) { + throw new Error(response.message); + } + return response.data; + } catch (error) { + return rejectWithValue(error.message); + } +}); + +export const fetchCampaigns = createAsyncThunk('brands/fetchCampaigns', async (_, { rejectWithValue }) => { + try { + const response = await api.get('/campaigns/'); + if (response.code !== 200) { + throw new Error(response.message); + } + return response.data; + } catch (error) { + return rejectWithValue(error.message); + } +}); + +export const createBrandThunk = createAsyncThunk('brands/createBrand', async (brand, { rejectWithValue, dispatch }) => { + try { + const response = await api.post('/brands/', brand); + if (response.code !== 201 && response.code !== 200) { + throw new Error(response.message); + } + } catch (error) { + dispatch(setNotificationBarMessage({ message: error.message, type: 'error' })); + return rejectWithValue(error.message); + } }); const initialState = { @@ -210,6 +272,38 @@ const brandsSlice = createSlice({ .addCase(fetchCampaigns.rejected, (state, action) => { state.status = 'failed'; state.error = action.error.message; + }) + .addCase(createBrandThunk.pending, (state) => { + state.status = 'loading'; + }) + .addCase(createBrandThunk.fulfilled, (state, action) => { + state.status = 'succeeded'; + }) + .addCase(createBrandThunk.rejected, (state, action) => { + state.status = 'failed'; + state.error = action.error.message; + }) + .addCase(fetchBrandDetail.pending, (state) => { + state.status = 'loading'; + }) + .addCase(fetchBrandDetail.fulfilled, (state, action) => { + state.status = 'succeeded'; + state.selectedBrand = action.payload; + }) + .addCase(fetchBrandDetail.rejected, (state, action) => { + state.status = 'failed'; + state.error = action.error.message; + }) + .addCase(fetchCampaignDetail.pending, (state) => { + state.status = 'loading'; + }) + .addCase(fetchCampaignDetail.fulfilled, (state, action) => { + state.status = 'succeeded'; + state.selectedCampaign = action.payload; + }) + .addCase(fetchCampaignDetail.rejected, (state, action) => { + state.status = 'failed'; + state.error = action.error.message; }); }, }); diff --git a/src/styles/custom-theme.scss b/src/styles/custom-theme.scss index 1090b2f..599300c 100644 --- a/src/styles/custom-theme.scss +++ b/src/styles/custom-theme.scss @@ -10,6 +10,17 @@ --bs-body-color: #171a1fff; --bs-btn-color: white !important; --bs-btn-hover-color: white !important; + --bs-form-valid-border-color: #6366f1 !important; + --bs-form-valid-bg-color: #6366f1 !important; + --bs-form-valid-color: #6366f1 !important; +} +select:focus { + box-shadow: 0 0 0 0.2rem rgba(99, 102, 241, 0.25) !important; + outline: none; +} +input:focus { + box-shadow: 0 0 0 0.2rem rgba(99, 102, 241, 0.25) !important; + outline: none; } .btn-primary {