diff --git a/package-lock.json b/package-lock.json index 8e0045e..ba056aa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,6 +16,7 @@ "@fortawesome/react-fontawesome": "^0.2.2", "@reduxjs/toolkit": "^2.8.1", "bootstrap": "^5.3.3", + "date-fns": "^4.1.0", "lodash": "^4.17.21", "lucide-react": "^0.508.0", "react": "^19.1.0", @@ -23,7 +24,8 @@ "react-bootstrap-range-slider": "^3.0.8", "react-dom": "^19.1.0", "react-redux": "^9.2.0", - "react-router-dom": "^7.6.0" + "react-router-dom": "^7.6.0", + "redux-persist": "^6.0.0" }, "devDependencies": { "@eslint/js": "^9.25.0", @@ -2240,6 +2242,15 @@ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==" }, + "node_modules/date-fns": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz", + "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/kossnocorp" + } + }, "node_modules/debug": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", @@ -4088,6 +4099,14 @@ "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==" }, + "node_modules/redux-persist": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/redux-persist/-/redux-persist-6.0.0.tgz", + "integrity": "sha512-71LLMbUq2r02ng2We9S215LtPu3fY0KgaGE0k8WRgl6RkqxtGfl7HUozz1Dftwsb0D/5mZ8dwAaPbtnzfvbEwQ==", + "peerDependencies": { + "redux": ">4.0.0" + } + }, "node_modules/redux-thunk": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", diff --git a/package.json b/package.json index 9c2a805..16ba84d 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "@fortawesome/react-fontawesome": "^0.2.2", "@reduxjs/toolkit": "^2.8.1", "bootstrap": "^5.3.3", + "date-fns": "^4.1.0", "lodash": "^4.17.21", "lucide-react": "^0.508.0", "react": "^19.1.0", @@ -25,7 +26,8 @@ "react-bootstrap-range-slider": "^3.0.8", "react-dom": "^19.1.0", "react-redux": "^9.2.0", - "react-router-dom": "^7.6.0" + "react-router-dom": "^7.6.0", + "redux-persist": "^6.0.0" }, "devDependencies": { "@eslint/js": "^9.25.0", diff --git a/src/components/BrandsList.jsx b/src/components/BrandsList.jsx new file mode 100644 index 0000000..00a809a --- /dev/null +++ b/src/components/BrandsList.jsx @@ -0,0 +1,57 @@ +import { useEffect } from 'react'; +import { useSelector, useDispatch } from 'react-redux'; +import { fetchBrands } from '../store/slices/brandsSlice'; +import { Card } from 'react-bootstrap'; +import { Folders, Hash, Link, Users } from 'lucide-react'; + +export default function BrandsList() { + const brands = useSelector((state) => state.brands.brands); + const dispatch = useDispatch(); + + useEffect(() => { + dispatch(fetchBrands()); + }, [dispatch]); + + return ( +
+ {brands.map((brand) => ( +
+ + + {brand.name.slice(0, 1)} + {brand.name.toUpperCase()} + + + + + Category + + {brand.category} + + + + + Collab. + + {brand.collab} + + + + + Creators + + {brand.creators} + + + + + Source + + {brand.source} + + +
+ ))} +
+ ); +} diff --git a/src/components/ChatInput.jsx b/src/components/ChatInput.jsx new file mode 100644 index 0000000..e4f58f4 --- /dev/null +++ b/src/components/ChatInput.jsx @@ -0,0 +1,26 @@ +import React, { useRef, useEffect } from 'react'; +import { Form } from 'react-bootstrap'; + +export default function ChatInput({ value, onChange }) { + const textareaRef = useRef(null); + + useEffect(() => { + const textarea = textareaRef.current; + if (textarea) { + textarea.style.height = 'auto'; // 先重设 + textarea.style.height = `${textarea.scrollHeight}px`; // 设置为内容高度 + } + }, [value]); + + return ( + + ); +} diff --git a/src/components/ChatWindow.jsx b/src/components/ChatWindow.jsx new file mode 100644 index 0000000..7f2583f --- /dev/null +++ b/src/components/ChatWindow.jsx @@ -0,0 +1,88 @@ +import { useEffect, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { fetchChatHistory } from '../store/slices/inboxSlice'; +import { Ellipsis, Send } from 'lucide-react'; +import { Button, Form } from 'react-bootstrap'; +import ChatInput from './ChatInput'; + +export default function ChatWindow() { + const { selectedChat, chatStatus } = useSelector((state) => state.inbox); + const [activePlatform, setActivePlatform] = useState('email'); + const dispatch = useDispatch(); + const [message, setMessage] = useState(''); + + const platformOptions = [ + { + name: 'Email', + value: 'email', + }, + { + name: 'WhatsApp', + value: 'whatsapp', + }, + ]; + + const handlePlatformChange = (value) => { + setActivePlatform(value); + }; + + useEffect(() => { + if (selectedChat) { + console.log(selectedChat); + } + }, [selectedChat]); + + const handleSendMessage = (e) => { + e.preventDefault(); + console.log(e.target.message.value); + }; + + return ( +
+
+
+
+ avatar +
+
+
{selectedChat.name}
+
+
+
+
+ {platformOptions.map((option) => ( +
handlePlatformChange(option.value)} + > + {option.name} +
+ ))} +
+
+ +
+
+
+
+
+ {selectedChat?.chatHistory?.length > 0 && + selectedChat?.chatHistory?.map((msg) => ( +
+ {msg.content} +
+ ))} +
+
+
+
+ setMessage(e.target.value)} /> + + +
+
+ ); +} diff --git a/src/components/DatabaseList.jsx b/src/components/DatabaseList.jsx index b10b133..02f7d87 100644 --- a/src/components/DatabaseList.jsx +++ b/src/components/DatabaseList.jsx @@ -24,7 +24,7 @@ export default function DatabaseList({ path }) { }, [dispatch, status]); useEffect(() => { - console.log(creators); + // console.log(creators); }, [creators]); // 处理全选/取消全选 const handleSelectAll = (e) => { diff --git a/src/components/InboxList.jsx b/src/components/InboxList.jsx new file mode 100644 index 0000000..bfe8b15 --- /dev/null +++ b/src/components/InboxList.jsx @@ -0,0 +1,98 @@ +import { useEffect, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { fetchChatHistory, fetchInboxList, selectChat } from '../store/slices/inboxSlice'; + +export default function InboxList() { + const dispatch = useDispatch(); + const { inboxList, inboxStatus: status, error, selectedChat } = useSelector((state) => state.inbox); + const [activeToggle, setActiveToggle] = useState('all'); + const [activeSelect, setActiveSelect] = useState('all'); + const toggleOptions = [ + { name: '全部', value: 'all' }, + { name: '首次建联', value: 'initial' }, + { name: '砍价邮件', value: 'bargain' }, + { name: '合作追踪', value: 'coop' }, + ]; + + useEffect(() => { + dispatch(fetchInboxList()); + }, [dispatch]); + + useEffect(() => { + if (inboxList.length > 0) { + // dispatch(selectChat(inboxList[0])); + // dispatch(fetchChatHistory(inboxList[0].id)); + } + }, [dispatch, inboxList]); + + const handleSelectChat = (item) => { + dispatch(selectChat(item)); + dispatch(fetchChatHistory(item.id)); + }; + + return ( +
+
+
Creator Inbox
+
+ +
+
+ {toggleOptions.map((option) => ( +
setActiveToggle(option.value)} + > + {option.name} +
+ ))} +
+
+ setActiveSelect('all')} + > + All + + setActiveSelect('unread')} + > + Unread + +
+
批量
+
+ +
+ {status === 'loading' &&
Loading...
} + {status === 'failed' &&
Error: {error}
} + {status === 'succeeded' && inboxList.length > 0 && + inboxList.map((item) => ( +
handleSelectChat(item)} + > +
+
+ {item.name} +
+
+
{item.name}
+
{item.message}
+
+
+
+
{item.date}
+ {item.unreadMessageCount > 0 && ( +
{item.unreadMessageCount}
+ )} +
+
+ ))} +
+
+ ); +} diff --git a/src/components/Layouts/DividLayout.jsx b/src/components/Layouts/DividLayout.jsx new file mode 100644 index 0000000..0c374e1 --- /dev/null +++ b/src/components/Layouts/DividLayout.jsx @@ -0,0 +1,15 @@ +import React, { useState } from 'react'; +import { Outlet } from 'react-router-dom'; +import Sidebar from './Sidebar'; + +export default function DividLayout() { + return ( +
+ + +
+ +
+
+ ); +} diff --git a/src/components/Layouts/MainLayout.jsx b/src/components/Layouts/MainLayout.jsx new file mode 100644 index 0000000..02ab258 --- /dev/null +++ b/src/components/Layouts/MainLayout.jsx @@ -0,0 +1,17 @@ +import React, { useState } from 'react'; +import { Outlet } from 'react-router-dom'; +import { Button } from 'react-bootstrap'; +import { List } from 'lucide-react'; +import Sidebar from './Sidebar'; + +export default function MainLayout() { + return ( +
+ + +
+ +
+
+ ); +} diff --git a/src/components/Sidebar.jsx b/src/components/Layouts/Sidebar.jsx similarity index 58% rename from src/components/Sidebar.jsx rename to src/components/Layouts/Sidebar.jsx index 6852027..2a1d31f 100644 --- a/src/components/Sidebar.jsx +++ b/src/components/Layouts/Sidebar.jsx @@ -1,10 +1,21 @@ -import React, { useState } from 'react'; +import React, { useState, useEffect } from 'react'; import { Link, useLocation } from 'react-router-dom'; import { Nav, Accordion } from 'react-bootstrap'; -import { Settings, ChevronDown, Blocks, SquareActivity, LayoutDashboard, Mail, UserSearch, Heart, Send, FileText } from 'lucide-react'; +import { + Settings, + ChevronDown, + Blocks, + SquareActivity, + LayoutDashboard, + Mail, + UserSearch, + Heart, + Send, + FileText, +} from 'lucide-react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import logo from '@/assets/logo.png'; -import '../styles/sidebar.scss'; +import '@/styles/sidebar.scss'; // Organized menu items const menuItems = [ @@ -12,14 +23,14 @@ const menuItems = [ id: 'creator-discovery', title: 'Creator Discovery', path: '/creator-discovery', - icon: , + icon: , hasSubmenu: false, }, { id: 'creator-database', title: 'Creator Database', path: '/creator-database', - icon: , + icon: , hasSubmenu: true, submenuItems: [ { @@ -38,7 +49,7 @@ const menuItems = [ id: 'youtube', title: 'YouTube', path: '/creator-database/youtube', - icon: , + icon: , }, ], }, @@ -61,7 +72,7 @@ const menuItems = [ id: 'brands', title: 'Brands', path: '/brands', - icon: , + icon: , hasSubmenu: false, }, { @@ -75,7 +86,7 @@ const menuItems = [ id: 'inbox', title: 'Inbox', path: '/creator-inbox', - icon: , + icon: , }, { id: 'templates', @@ -97,6 +108,20 @@ const menuItems = [ export default function Sidebar() { const location = useLocation(); const [expanded, setExpanded] = useState({}); + const [collapsed, setCollapsed] = useState(false); + + // 监听窗口大小变化 + useEffect(() => { + const handleResize = () => { + setCollapsed(window.innerWidth < 768); + }; + + // 初始化 + handleResize(); + + window.addEventListener('resize', handleResize); + return () => window.removeEventListener('resize', handleResize); + }, []); // 检查路径是否匹配当前路由 const isActive = (path) => { @@ -118,13 +143,15 @@ export default function Sidebar() { }; return ( -
+
OOIN Logo -
-
OOIN Media
-
Creator Center
-
+ {!collapsed && ( +
+
OOIN Media
+
Creator Center
+
+ )}