Commit a42cb84d by HoMeTown

feat: 封装HOC

parent a7ecdeb9
...@@ -3,14 +3,17 @@ import React from 'react' ...@@ -3,14 +3,17 @@ import React from 'react'
import { BrowserRouter as Router } from 'react-router-dom' import { BrowserRouter as Router } from 'react-router-dom'
import { MainLayout } from './layouts' import { MainLayout } from './layouts'
import { AppRoutes } from './routes/AppRoutes' import { AppRoutes } from './routes/AppRoutes'
import { AuthProvider } from './auth/AuthContext'
const App: React.FC = () => { const App: React.FC = () => {
return ( return (
<Router> <AuthProvider>
<MainLayout> <Router>
<AppRoutes /> <MainLayout>
</MainLayout> <AppRoutes />
</Router> </MainLayout>
</Router>
</AuthProvider>
) )
} }
......
// AuthContext.tsx
import type { ReactNode } from 'react'
import React, { createContext, useContext, useState } from 'react'
import { useLocalStorageState } from 'ahooks'
interface AuthContextType {
isLoggedIn: boolean
showLoginModal: boolean
login: () => void
logout: () => void
toggleLoginModal: () => void
}
const AuthContext = createContext<AuthContextType | undefined>(undefined)
interface AuthProviderProps {
children: ReactNode
}
export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
const [token] = useLocalStorageState<string | undefined>(
'__TOKEN__',
{
defaultValue: '',
},
)
const [isLoggedIn, setIsLoggedIn] = useState(Boolean(token))
const [showLoginModal, setShowLoginModal] = useState(false)
const login = () => {
setIsLoggedIn(true)
setShowLoginModal(false)
}
const logout = () => {
setIsLoggedIn(false)
}
const toggleLoginModal = () => {
setShowLoginModal(!showLoginModal)
}
return (
// eslint-disable-next-line react/no-unstable-context-value
<AuthContext.Provider value={{ isLoggedIn, showLoginModal, login, logout, toggleLoginModal }}>
{children}
</AuthContext.Provider>
)
}
// eslint-disable-next-line react-refresh/only-export-components
export function useAuth(): AuthContextType {
const context = useContext(AuthContext)
if (context === undefined) {
throw new Error('useAuth must be used within an AuthProvider')
}
return context
}
// withAuth.tsx
import React from 'react'
import { useAuth } from './AuthContext'
export interface WithAuthProps {
checkAuth: () => boolean
}
export function withAuth<T extends WithAuthProps = WithAuthProps>(
WrappedComponent: React.ComponentType<T>,
) {
return (props: Omit<T, keyof WithAuthProps>) => {
const { isLoggedIn, toggleLoginModal } = useAuth()
const checkAuth = () => {
if (!isLoggedIn) {
toggleLoginModal()
return false
}
return true
}
return <WrappedComponent {...(props as T)} checkAuth={checkAuth} />
}
}
import React, { useEffect, useRef, useState } from 'react' import React, { useEffect, useRef, useState } from 'react'
import { AnimatePresence, motion } from 'framer-motion' import { AnimatePresence, motion } from 'framer-motion'
import { Button } from '@nextui-org/react' import { Button } from '@nextui-org/react'
import { useLocalStorageState, useToggle } from 'ahooks' import { useToggle } from 'ahooks'
import { LoginModal } from '../LoginModal' import { LoginModal } from '../LoginModal'
import SendIcon from '@/assets/svg/send.svg?react' import SendIcon from '@/assets/svg/send.svg?react'
import { type WithAuthProps, withAuth } from '@/auth/withAuth'
interface EditorProps { interface ChatEditorProps {
onChange?: (value: string) => void onChange?: (value: string) => void
onFocus?: () => void onFocus?: () => void
onSubmit?: (value: string) => void onSubmit?: (value: string) => void
placeholders: string[] placeholders: string[]
} }
const Editor: React.FC<EditorProps> = ({ onChange, onFocus, onSubmit, placeholders }) => { const ChatEditorBase: React.FC<ChatEditorProps & WithAuthProps> = ({ checkAuth, onChange, onFocus, onSubmit, placeholders }) => {
const [content, setContent] = useState('') const [content, setContent] = useState('')
const editorRef = useRef<HTMLDivElement>(null) const editorRef = useRef<HTMLDivElement>(null)
const [currentPlaceholder, setCurrentPlaceholder] = useState(0) const [currentPlaceholder, setCurrentPlaceholder] = useState(0)
const intervalRef = useRef<NodeJS.Timeout | null>(null) const intervalRef = useRef<NodeJS.Timeout | null>(null)
const [token] = useLocalStorageState<string | undefined>('__TOKEN__', {
defaultValue: '',
listenStorageChange: true,
})
const [isOpenLoginModal, isOpenLoginModalActions] = useToggle() const [isOpenLoginModal, isOpenLoginModalActions] = useToggle()
const startAnimation = () => { const startAnimation = () => {
...@@ -47,15 +44,13 @@ const Editor: React.FC<EditorProps> = ({ onChange, onFocus, onSubmit, placeholde ...@@ -47,15 +44,13 @@ const Editor: React.FC<EditorProps> = ({ onChange, onFocus, onSubmit, placeholde
} }
const handleSubmit = () => { const handleSubmit = () => {
if (!token) { if (checkAuth()) {
isOpenLoginModalActions.setRight() if (content.trim()) {
return onSubmit?.(content.trim())
} setContent('')
if (content.trim()) { if (editorRef.current) {
onSubmit?.(content.trim()) editorRef.current.textContent = ''
setContent('') }
if (editorRef.current) {
editorRef.current.textContent = ''
} }
} }
} }
...@@ -139,4 +134,4 @@ const Editor: React.FC<EditorProps> = ({ onChange, onFocus, onSubmit, placeholde ...@@ -139,4 +134,4 @@ const Editor: React.FC<EditorProps> = ({ onChange, onFocus, onSubmit, placeholde
) )
} }
export default Editor export const ChatEditor = withAuth(ChatEditorBase)
...@@ -4,23 +4,27 @@ import { useToggle } from 'ahooks' ...@@ -4,23 +4,27 @@ import { useToggle } from 'ahooks'
import { Navbar } from '../Navbar' import { Navbar } from '../Navbar'
import { HistoryBar } from '../HistoryBar/HistoryBar' import { HistoryBar } from '../HistoryBar/HistoryBar'
import styles from './MainLayout.module.less' import styles from './MainLayout.module.less'
import { useAuth } from '@/auth/AuthContext'
import { LoginModal } from '@/components/LoginModal'
interface MainLayoutProps { interface MainLayoutProps {
children: React.ReactNode children: React.ReactNode
} }
const contentVariants = {
expanded: {
width: '90px',
transition: { type: 'spring', stiffness: 300, damping: 30 },
},
shrunk: {
width: '340px',
transition: { type: 'spring', stiffness: 300, damping: 30 },
},
}
export const MainLayout: React.FC<MainLayoutProps> = ({ children }) => { export const MainLayout: React.FC<MainLayoutProps> = ({ children }) => {
const { showLoginModal, toggleLoginModal } = useAuth()
const [isHistoryVisible, { toggle }] = useToggle() const [isHistoryVisible, { toggle }] = useToggle()
const contentVariants = {
expanded: {
width: '90px',
transition: { type: 'spring', stiffness: 300, damping: 30 },
},
shrunk: {
width: '340px',
transition: { type: 'spring', stiffness: 300, damping: 30 },
},
}
return ( return (
<motion.main className={styles.layoutMain}> <motion.main className={styles.layoutMain}>
<motion.div <motion.div
...@@ -36,6 +40,8 @@ export const MainLayout: React.FC<MainLayoutProps> = ({ children }) => { ...@@ -36,6 +40,8 @@ export const MainLayout: React.FC<MainLayoutProps> = ({ children }) => {
> >
{children} {children}
</motion.div> </motion.div>
<LoginModal isOpen={showLoginModal} onClose={toggleLoginModal} />
</motion.main> </motion.main>
) )
} }
...@@ -12,11 +12,8 @@ import Collect from '@/assets/svg/collect.svg?react' ...@@ -12,11 +12,8 @@ import Collect from '@/assets/svg/collect.svg?react'
import Tools from '@/assets/svg/tools.svg?react' import Tools from '@/assets/svg/tools.svg?react'
import UserIcon from '@/assets/svg/user.svg?react' import UserIcon from '@/assets/svg/user.svg?react'
import { LoginModal } from '@/components/LoginModal' import { LoginModal } from '@/components/LoginModal'
import type { WithAuthProps } from '@/auth/withAuth'
interface NavbarProps { import { withAuth } from '@/auth/withAuth'
isCollapsed: boolean
onToggle: () => void
}
const NAV_BAR_ITEMS = [ const NAV_BAR_ITEMS = [
{ icon: Logo, label: '', key: 'logo' }, { icon: Logo, label: '', key: 'logo' },
...@@ -29,7 +26,12 @@ const NAV_BAR_ITEMS = [ ...@@ -29,7 +26,12 @@ const NAV_BAR_ITEMS = [
{ icon: '', label: '', key: 'line3' }, { icon: '', label: '', key: 'line3' },
] ]
export const Navbar: React.FC<NavbarProps> = ({ onToggle }) => { interface NavbarProps {
isCollapsed: boolean
onToggle: () => void
}
const NavbarBase: React.FC<NavbarProps & WithAuthProps> = ({ checkAuth, onToggle }) => {
const [token] = useLocalStorageState<string | undefined>('__TOKEN__', { const [token] = useLocalStorageState<string | undefined>('__TOKEN__', {
defaultValue: '', defaultValue: '',
listenStorageChange: true, listenStorageChange: true,
...@@ -51,23 +53,13 @@ export const Navbar: React.FC<NavbarProps> = ({ onToggle }) => { ...@@ -51,23 +53,13 @@ export const Navbar: React.FC<NavbarProps> = ({ onToggle }) => {
}, 1000) }, 1000)
} }
const handleLogin = () => {
if (!token) {
isOpenLogTipActions.setLeft()
isOpenLoginModalActions.setRight()
return false
}
return true
}
const handleCloseLoginModal = () => { const handleCloseLoginModal = () => {
isOpenLoginModalActions.setLeft() isOpenLoginModalActions.setLeft()
handleShowLoginTip() handleShowLoginTip()
} }
const handleClick = (type: string | undefined) => { const handleClick = (type: string | undefined) => {
if (!handleLogin()) if (!checkAuth())
return return
if (type === 'history') { if (type === 'history') {
...@@ -89,7 +81,7 @@ export const Navbar: React.FC<NavbarProps> = ({ onToggle }) => { ...@@ -89,7 +81,7 @@ export const Navbar: React.FC<NavbarProps> = ({ onToggle }) => {
) )
})} })}
<Tooltip isOpen={isOpenLogTip} color="foreground" content="登录体验更多功能" placement="right"> <Tooltip isOpen={isOpenLogTip} color="foreground" content="登录体验更多功能" placement="right">
<Button onClick={handleLogin} variant="light" isIconOnly aria-label="Like"> <Button onClick={checkAuth} variant="light" isIconOnly aria-label="Like">
<UserIcon /> <UserIcon />
</Button> </Button>
</Tooltip> </Tooltip>
...@@ -99,3 +91,5 @@ export const Navbar: React.FC<NavbarProps> = ({ onToggle }) => { ...@@ -99,3 +91,5 @@ export const Navbar: React.FC<NavbarProps> = ({ onToggle }) => {
</> </>
) )
} }
export const Navbar = withAuth(NavbarBase)
...@@ -8,7 +8,7 @@ import { Slogan } from './components/Slogan/Slogan' ...@@ -8,7 +8,7 @@ import { Slogan } from './components/Slogan/Slogan'
import HomeIcon1 from '@/assets/homeIcon1.png' import HomeIcon1 from '@/assets/homeIcon1.png'
import HomeIcon2 from '@/assets/homeIcon2.png' import HomeIcon2 from '@/assets/homeIcon2.png'
import { GradientBackground } from '@/components/GradientBackground' import { GradientBackground } from '@/components/GradientBackground'
import ChatEditor from '@/components/ChatEditor' import { ChatEditor } from '@/components/ChatEditor'
export const Home: React.FC = () => { export const Home: React.FC = () => {
const placeholders = [ const placeholders = [
...@@ -19,11 +19,6 @@ export const Home: React.FC = () => { ...@@ -19,11 +19,6 @@ export const Home: React.FC = () => {
'保险公司偿付能力在哪里可以看?', '保险公司偿付能力在哪里可以看?',
] ]
// async function getRecommendQuestionList() {
// const res = await fetchRecommendQuestionList()
// console.log(res)
// }
useEffect(() => { useEffect(() => {
// getRecommendQuestionList() // getRecommendQuestionList()
}, []) }, [])
...@@ -51,7 +46,6 @@ export const Home: React.FC = () => { ...@@ -51,7 +46,6 @@ export const Home: React.FC = () => {
</div> </div>
)} )}
/> />
</div> </div>
<div className="box-border px-[0] mx-auto iptContainer w-full max-w-[1000px] flex-shrink-0 sm:px-0 pb-[18px]"> <div className="box-border px-[0] mx-auto iptContainer w-full max-w-[1000px] flex-shrink-0 sm:px-0 pb-[18px]">
<ChatEditor placeholders={placeholders} /> <ChatEditor placeholders={placeholders} />
......
...@@ -4,6 +4,7 @@ import { Image } from '@nextui-org/image' ...@@ -4,6 +4,7 @@ import { Image } from '@nextui-org/image'
import { motion } from 'framer-motion' import { motion } from 'framer-motion'
import { useState } from 'react' import { useState } from 'react'
import Refresh from '@/assets/svg/refresh.svg?react' import Refresh from '@/assets/svg/refresh.svg?react'
import { type WithAuthProps, withAuth } from '@/auth/withAuth'
interface QuestionListProps { interface QuestionListProps {
title: string title: string
...@@ -32,11 +33,14 @@ const item = { ...@@ -32,11 +33,14 @@ const item = {
}, },
} }
export const QuestionList: React.FC<QuestionListProps> = ({ dotColor, title, iconImg, showRefresh = true }) => { const QuestionListBase: React.FC<QuestionListProps & WithAuthProps> = ({ checkAuth, dotColor, title, iconImg, showRefresh = true }) => {
const [isRotating, setIsRotating] = useState(false) const [isRotating, setIsRotating] = useState(false)
const handleRefresh = () => { const handleRefresh = () => {
setIsRotating(true) setIsRotating(true)
} }
const handleClick = () => {
checkAuth()
}
return ( return (
<div className="h-[276px] bg-white box-border px-[20px] py-[24px] rounded-[24px] w-full sm:w-[360px] md:w-[300px]"> <div className="h-[276px] bg-white box-border px-[20px] py-[24px] rounded-[24px] w-full sm:w-[360px] md:w-[300px]">
<h3 className="h-[32px] flex items-center justify-between"> <h3 className="h-[32px] flex items-center justify-between">
...@@ -80,37 +84,7 @@ export const QuestionList: React.FC<QuestionListProps> = ({ dotColor, title, ico ...@@ -80,37 +84,7 @@ export const QuestionList: React.FC<QuestionListProps> = ({ dotColor, title, ico
<motion.li <motion.li
variants={item} variants={item}
> >
<Button color="primary" variant="flat" className="w-full bg-[#F7FCFF] "> <Button onClick={handleClick} color="primary" variant="flat" className="w-full bg-[#F7FCFF] ">
<div className="w-full text-nowrap text-ellipsis overflow-hidden text-[#82969C]">
<span style={{ color: dotColor }}>·</span>
<span className="ml-[8px]">推荐几款60周岁还能投的医疗保推荐几款60周岁还能投的医疗保</span>
</div>
</Button>
</motion.li>
<motion.li
variants={item}
>
<Button color="primary" variant="flat" className="w-full bg-[#F7FCFF] ">
<div className="w-full text-nowrap text-ellipsis overflow-hidden text-[#82969C]">
<span style={{ color: dotColor }}>·</span>
<span className="ml-[8px]">推荐几款60周岁还能投的医疗保推荐几款60周岁还能投的医疗保</span>
</div>
</Button>
</motion.li>
<motion.li
variants={item}
>
<Button color="primary" variant="flat" className="w-full bg-[#F7FCFF] ">
<div className="w-full text-nowrap text-ellipsis overflow-hidden text-[#82969C]">
<span style={{ color: dotColor }}>·</span>
<span className="ml-[8px]">推荐几款60周岁还能投的医疗保推荐几款60周岁还能投的医疗保</span>
</div>
</Button>
</motion.li>
<motion.li
variants={item}
>
<Button color="primary" variant="flat" className="w-full bg-[#F7FCFF] ">
<div className="w-full text-nowrap text-ellipsis overflow-hidden text-[#82969C]"> <div className="w-full text-nowrap text-ellipsis overflow-hidden text-[#82969C]">
<span style={{ color: dotColor }}>·</span> <span style={{ color: dotColor }}>·</span>
<span className="ml-[8px]">推荐几款60周岁还能投的医疗保推荐几款60周岁还能投的医疗保</span> <span className="ml-[8px]">推荐几款60周岁还能投的医疗保推荐几款60周岁还能投的医疗保</span>
...@@ -121,3 +95,5 @@ export const QuestionList: React.FC<QuestionListProps> = ({ dotColor, title, ico ...@@ -121,3 +95,5 @@ export const QuestionList: React.FC<QuestionListProps> = ({ dotColor, title, ico
</div> </div>
) )
} }
export const QuestionList = withAuth(QuestionListBase)
...@@ -7,6 +7,7 @@ import BotImg from '@/assets/bot.png' ...@@ -7,6 +7,7 @@ import BotImg from '@/assets/bot.png'
import BotPopImg from '@/assets/botPop.png' import BotPopImg from '@/assets/botPop.png'
import BotBgImg from '@/assets/botBg.png' import BotBgImg from '@/assets/botBg.png'
import SayHi from '@/assets/svg/sayHi.svg?react' import SayHi from '@/assets/svg/sayHi.svg?react'
import { type WithAuthProps, withAuth } from '@/auth/withAuth'
const BotEye: React.FC = () => { const BotEye: React.FC = () => {
const controls = useAnimation() const controls = useAnimation()
...@@ -52,7 +53,10 @@ const BotAnimateBox: React.FC = () => { ...@@ -52,7 +53,10 @@ const BotAnimateBox: React.FC = () => {
) )
} }
export const WelcomeWord: React.FC = () => { const WelcomeWordBase: React.FC<WithAuthProps> = ({ checkAuth }) => {
const handleGo = () => {
checkAuth()
}
return ( return (
<div className="w-full h-auto flex-shrink-0 relative sm:w-[360px] sm:h-[276px]"> <div className="w-full h-auto flex-shrink-0 relative sm:w-[360px] sm:h-[276px]">
<img className="absolute z-[-1] top-0 left-0" src={BotBgImg} alt="" /> <img className="absolute z-[-1] top-0 left-0" src={BotBgImg} alt="" />
...@@ -60,8 +64,9 @@ export const WelcomeWord: React.FC = () => { ...@@ -60,8 +64,9 @@ export const WelcomeWord: React.FC = () => {
<div className="relative z-[1] box-border px-[24px] pt-[68px]"> <div className="relative z-[1] box-border px-[24px] pt-[68px]">
<SayHi /> <SayHi />
<p className="text-[#27353C] text-[15px] mt-[24px] mb-[16px]">做为您的智能保险伙伴,您有各类专业相关的问题都可以抛给我哟~让我们互相帮助共同成长吧~</p> <p className="text-[#27353C] text-[15px] mt-[24px] mb-[16px]">做为您的智能保险伙伴,您有各类专业相关的问题都可以抛给我哟~让我们互相帮助共同成长吧~</p>
<Button className="bg-white text-[#20ABD9] font-medium rounded-[20px]">立即前往 ➔</Button> <Button className="bg-white text-[#20ABD9] font-medium rounded-[20px]" onClick={handleGo}>立即前往 ➔</Button>
</div> </div>
</div> </div>
) )
} }
export const WelcomeWord = withAuth(WelcomeWordBase)
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment