Commit a42cb84d by HoMeTown

feat: 封装HOC

parent a7ecdeb9
......@@ -3,14 +3,17 @@ import React from 'react'
import { BrowserRouter as Router } from 'react-router-dom'
import { MainLayout } from './layouts'
import { AppRoutes } from './routes/AppRoutes'
import { AuthProvider } from './auth/AuthContext'
const App: React.FC = () => {
return (
<AuthProvider>
<Router>
<MainLayout>
<AppRoutes />
</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 { AnimatePresence, motion } from 'framer-motion'
import { Button } from '@nextui-org/react'
import { useLocalStorageState, useToggle } from 'ahooks'
import { useToggle } from 'ahooks'
import { LoginModal } from '../LoginModal'
import SendIcon from '@/assets/svg/send.svg?react'
import { type WithAuthProps, withAuth } from '@/auth/withAuth'
interface EditorProps {
interface ChatEditorProps {
onChange?: (value: string) => void
onFocus?: () => void
onSubmit?: (value: string) => void
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 editorRef = useRef<HTMLDivElement>(null)
const [currentPlaceholder, setCurrentPlaceholder] = useState(0)
const intervalRef = useRef<NodeJS.Timeout | null>(null)
const [token] = useLocalStorageState<string | undefined>('__TOKEN__', {
defaultValue: '',
listenStorageChange: true,
})
const [isOpenLoginModal, isOpenLoginModalActions] = useToggle()
const startAnimation = () => {
......@@ -47,10 +44,7 @@ const Editor: React.FC<EditorProps> = ({ onChange, onFocus, onSubmit, placeholde
}
const handleSubmit = () => {
if (!token) {
isOpenLoginModalActions.setRight()
return
}
if (checkAuth()) {
if (content.trim()) {
onSubmit?.(content.trim())
setContent('')
......@@ -59,6 +53,7 @@ const Editor: React.FC<EditorProps> = ({ onChange, onFocus, onSubmit, placeholde
}
}
}
}
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' && !e.shiftKey) {
......@@ -139,4 +134,4 @@ const Editor: React.FC<EditorProps> = ({ onChange, onFocus, onSubmit, placeholde
)
}
export default Editor
export const ChatEditor = withAuth(ChatEditorBase)
......@@ -4,14 +4,14 @@ import { useToggle } from 'ahooks'
import { Navbar } from '../Navbar'
import { HistoryBar } from '../HistoryBar/HistoryBar'
import styles from './MainLayout.module.less'
import { useAuth } from '@/auth/AuthContext'
import { LoginModal } from '@/components/LoginModal'
interface MainLayoutProps {
children: React.ReactNode
}
export const MainLayout: React.FC<MainLayoutProps> = ({ children }) => {
const [isHistoryVisible, { toggle }] = useToggle()
const contentVariants = {
const contentVariants = {
expanded: {
width: '90px',
transition: { type: 'spring', stiffness: 300, damping: 30 },
......@@ -20,7 +20,11 @@ export const MainLayout: React.FC<MainLayoutProps> = ({ children }) => {
width: '340px',
transition: { type: 'spring', stiffness: 300, damping: 30 },
},
}
}
export const MainLayout: React.FC<MainLayoutProps> = ({ children }) => {
const { showLoginModal, toggleLoginModal } = useAuth()
const [isHistoryVisible, { toggle }] = useToggle()
return (
<motion.main className={styles.layoutMain}>
<motion.div
......@@ -36,6 +40,8 @@ export const MainLayout: React.FC<MainLayoutProps> = ({ children }) => {
>
{children}
</motion.div>
<LoginModal isOpen={showLoginModal} onClose={toggleLoginModal} />
</motion.main>
)
}
......@@ -12,11 +12,8 @@ import Collect from '@/assets/svg/collect.svg?react'
import Tools from '@/assets/svg/tools.svg?react'
import UserIcon from '@/assets/svg/user.svg?react'
import { LoginModal } from '@/components/LoginModal'
interface NavbarProps {
isCollapsed: boolean
onToggle: () => void
}
import type { WithAuthProps } from '@/auth/withAuth'
import { withAuth } from '@/auth/withAuth'
const NAV_BAR_ITEMS = [
{ icon: Logo, label: '', key: 'logo' },
......@@ -29,7 +26,12 @@ const NAV_BAR_ITEMS = [
{ 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__', {
defaultValue: '',
listenStorageChange: true,
......@@ -51,23 +53,13 @@ export const Navbar: React.FC<NavbarProps> = ({ onToggle }) => {
}, 1000)
}
const handleLogin = () => {
if (!token) {
isOpenLogTipActions.setLeft()
isOpenLoginModalActions.setRight()
return false
}
return true
}
const handleCloseLoginModal = () => {
isOpenLoginModalActions.setLeft()
handleShowLoginTip()
}
const handleClick = (type: string | undefined) => {
if (!handleLogin())
if (!checkAuth())
return
if (type === 'history') {
......@@ -89,7 +81,7 @@ export const Navbar: React.FC<NavbarProps> = ({ onToggle }) => {
)
})}
<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 />
</Button>
</Tooltip>
......@@ -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'
import HomeIcon1 from '@/assets/homeIcon1.png'
import HomeIcon2 from '@/assets/homeIcon2.png'
import { GradientBackground } from '@/components/GradientBackground'
import ChatEditor from '@/components/ChatEditor'
import { ChatEditor } from '@/components/ChatEditor'
export const Home: React.FC = () => {
const placeholders = [
......@@ -19,11 +19,6 @@ export const Home: React.FC = () => {
'保险公司偿付能力在哪里可以看?',
]
// async function getRecommendQuestionList() {
// const res = await fetchRecommendQuestionList()
// console.log(res)
// }
useEffect(() => {
// getRecommendQuestionList()
}, [])
......@@ -51,7 +46,6 @@ export const Home: React.FC = () => {
</div>
)}
/>
</div>
<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} />
......
......@@ -4,6 +4,7 @@ import { Image } from '@nextui-org/image'
import { motion } from 'framer-motion'
import { useState } from 'react'
import Refresh from '@/assets/svg/refresh.svg?react'
import { type WithAuthProps, withAuth } from '@/auth/withAuth'
interface QuestionListProps {
title: string
......@@ -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 handleRefresh = () => {
setIsRotating(true)
}
const handleClick = () => {
checkAuth()
}
return (
<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">
......@@ -80,37 +84,7 @@ export const QuestionList: React.FC<QuestionListProps> = ({ dotColor, title, ico
<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]">
<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] ">
<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>
......@@ -121,3 +95,5 @@ export const QuestionList: React.FC<QuestionListProps> = ({ dotColor, title, ico
</div>
)
}
export const QuestionList = withAuth(QuestionListBase)
......@@ -7,6 +7,7 @@ import BotImg from '@/assets/bot.png'
import BotPopImg from '@/assets/botPop.png'
import BotBgImg from '@/assets/botBg.png'
import SayHi from '@/assets/svg/sayHi.svg?react'
import { type WithAuthProps, withAuth } from '@/auth/withAuth'
const BotEye: React.FC = () => {
const controls = useAnimation()
......@@ -52,7 +53,10 @@ const BotAnimateBox: React.FC = () => {
)
}
export const WelcomeWord: React.FC = () => {
const WelcomeWordBase: React.FC<WithAuthProps> = ({ checkAuth }) => {
const handleGo = () => {
checkAuth()
}
return (
<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="" />
......@@ -60,8 +64,9 @@ export const WelcomeWord: React.FC = () => {
<div className="relative z-[1] box-border px-[24px] pt-[68px]">
<SayHi />
<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>
)
}
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