diff --git a/src/pages/FAQKitchenPage.stories.tsx b/src/pages/FAQKitchenPage.stories.tsx new file mode 100644 index 0000000..899568c --- /dev/null +++ b/src/pages/FAQKitchenPage.stories.tsx @@ -0,0 +1,17 @@ +import type { Meta, StoryObj } from '@storybook/react-vite' +import { FAQKitchenPage, FAQTagShowcase } from './FAQKitchenPage' + +const meta = { + title: 'Apps/FAQ Kitchen', + component: FAQKitchenPage, + parameters: { layout: 'fullscreen' }, +} satisfies Meta + +export default meta +type Story = StoryObj + +export const Default: Story = {} + +export const TagShowcase: Story = { + render: () => , +} diff --git a/src/pages/FAQKitchenPage.tsx b/src/pages/FAQKitchenPage.tsx new file mode 100644 index 0000000..eaf512d --- /dev/null +++ b/src/pages/FAQKitchenPage.tsx @@ -0,0 +1,1279 @@ +/* eslint-disable react/no-array-index-key */ +import React, { useMemo, useRef, useState, useEffect } from 'react' +import { styled, useTheme } from 'styled-components' +import { Box, Flex, Grid } from '../primitives/Box' +import { Heading } from '../primitives/Heading' +import { Text } from '../primitives/Text' +import { Button, IconButton } from '../primitives/Button' +import { Card, CardBody } from '../primitives/Card' +import { Input } from '../primitives/Input' +import { Tag } from '../primitives/Tag' +import { Modal, ModalProvider, useModal } from '../primitives/Modal' +import { + SearchIcon, + ChevronDownIcon, + ChevronLeftIcon, + CloseIcon, + OpenNewIcon, +} from '../primitives/Icons' + +// ---------- Data ---------- + +type Persona = 'new' | 'trader' | 'earner' | 'all' + +interface FAQItem { + id: number + q: string + a: string + cat: string + icon: string + seg: Persona[] + doc: string + shill: { t: string; l: string } | null +} + +const FAQS: FAQItem[] = [ + { + id: 1, + q: 'What is PancakeSwap?', + a: "PancakeSwap is the world's most popular decentralized exchange. Swap tokens, earn rewards, and trade β€” all without giving up control of your crypto. No accounts, no KYC, just connect and go.", + cat: 'Welcome', + icon: 'πŸ₯ž', + seg: ['new'], + doc: 'https://docs.pancakeswap.finance/', + shill: { t: 'Try your first swap β†’', l: 'https://pancakeswap.finance/swap' }, + }, + { + id: 2, + q: 'How do I connect my wallet?', + a: "Click 'Connect Wallet' on pancakeswap.finance. Use MetaMask, Trust Wallet, Binance Wallet, or social login (no seed phrase!). Make sure you're on BNB Chain.", + cat: 'Welcome', + icon: 'πŸ’³', + seg: ['new'], + doc: 'https://docs.pancakeswap.finance/get-started/connection-guide', + shill: { t: 'Connect & swap β†’', l: 'https://pancakeswap.finance/swap' }, + }, + { + id: 3, + q: 'How do I make my first swap?', + a: 'Connect wallet β†’ pick tokens (e.g. BNB β†’ CAKE) β†’ enter amount β†’ hit Swap β†’ confirm in wallet. Done in under a minute.', + cat: 'Welcome', + icon: '⚑', + seg: ['new'], + doc: 'https://docs.pancakeswap.finance/products/pancakeswap-exchange', + shill: { t: 'Make your first swap β†’', l: 'https://pancakeswap.finance/swap' }, + }, + { + id: 4, + q: 'What is social login?', + a: 'Create a self-custodial wallet with just Google or email. No seed phrase, no extension. The easiest onramp to PancakeSwap.', + cat: 'Welcome', + icon: 'πŸ”“', + seg: ['new'], + doc: 'https://docs.pancakeswap.finance/', + shill: null, + }, + { + id: 5, + q: "What are PancakeSwap's fees?", + a: 'V2: flat 0.25%. V3: flexible tiers (0.01%, 0.05%, 0.25%, 1%). No frontend surcharge. BNB Chain gas is usually pennies.', + cat: 'Trading', + icon: 'πŸ’°', + seg: ['new', 'trader'], + doc: 'https://docs.pancakeswap.finance/products/pancakeswap-exchange/faq', + shill: { t: 'See fees live β†’', l: 'https://pancakeswap.finance/swap' }, + }, + { + id: 6, + q: 'What is slippage and how do I set it?', + a: 'The gap between expected and actual price. Set via gear icon: 0.5% for most tokens, 1-5% for volatile ones. Failed swap? Increase slippage.', + cat: 'Trading', + icon: '🎯', + seg: ['trader'], + doc: 'https://docs.pancakeswap.finance/products/pancakeswap-exchange/faq', + shill: null, + }, + { + id: 7, + q: "How do I fix 'Insufficient output amount'?", + a: "Slippage too low or thin liquidity. Fix: (1) bump slippage to 1-3%, (2) smaller amount, (3) try later. Can't sell at all? Might be a scam token.", + cat: 'Troubleshooting', + icon: 'πŸ”§', + seg: ['new', 'trader'], + doc: 'https://docs.pancakeswap.finance/welcome-to-pancakeswap/contact-us/faq/troubleshooting', + shill: null, + }, + { + id: 8, + q: 'Which chains are supported?', + a: '9+ chains: BNB Chain, Ethereum, Arbitrum, Base, Solana, zkSync Era, Linea, opBNB, Monad. Bridge tokens directly in-app.', + cat: 'Trading', + icon: '🌍', + seg: ['trader'], + doc: 'https://docs.pancakeswap.finance/', + shill: { t: 'Cross-chain swap β†’', l: 'https://pancakeswap.finance/swap' }, + }, + { + id: 9, + q: 'How do I provide liquidity?', + a: 'Liquidity page β†’ pick pair β†’ choose V3/Infinity β†’ set price range β†’ deposit. Earn fees from every swap in your pool.', + cat: 'Earning', + icon: 'πŸ’§', + seg: ['earner'], + doc: 'https://docs.pancakeswap.finance/products/pancakeswap-exchange/liquidity-guide', + shill: { t: 'Add liquidity β†’', l: 'https://pancakeswap.finance/liquidity' }, + }, + { + id: 10, + q: 'What is impermanent loss?', + a: "When your deposited tokens' price ratio shifts vs. holding. It's 'impermanent' β€” reverses if prices return. Fees often offset it, but it's the LP's key risk.", + cat: 'Earning', + icon: '⚠️', + seg: ['earner'], + doc: 'https://docs.pancakeswap.finance/products/pancakeswap-exchange/liquidity-guide', + shill: null, + }, + { + id: 11, + q: 'How do I stake CAKE?', + a: 'Syrup Pools page β†’ stake CAKE β†’ earn CAKE or partner tokens. Flexible = withdraw anytime. Fixed = higher APR, locked period.', + cat: 'Earning', + icon: '🍰', + seg: ['earner'], + doc: 'https://docs.pancakeswap.finance/products/syrup-pool', + shill: { t: 'Stake CAKE β†’', l: 'https://pancakeswap.finance/pools' }, + }, + { + id: 12, + q: 'What is PancakeSwap Infinity?', + a: 'The latest liquidity engine (April 2025). CLAMM + LBAMM pools, custom fees, programmable hooks, less gas. V3, evolved.', + cat: 'Product', + icon: '♾️', + seg: ['earner', 'trader'], + doc: 'https://docs.pancakeswap.finance/', + shill: { t: 'Explore Infinity β†’', l: 'https://pancakeswap.finance/liquidity' }, + }, + { + id: 13, + q: 'What is CAKE Tokenomics 3.0?', + a: 'Buy-and-burn model (April 2025). veCAKE retired, emissions cut to ~22k/day, max supply now 400M. Deflationary for 29+ months.', + cat: 'Tokenomics', + icon: 'πŸ”₯', + seg: ['earner'], + doc: 'https://docs.pancakeswap.finance/protocol/cake-tokenomics', + shill: { + t: 'Buy CAKE β†’', + l: 'https://pancakeswap.finance/swap?outputCurrency=0x0E09FaBB73Bd3Ade0a17ECC321fD13a19e81cE82', + }, + }, + { + id: 14, + q: 'How do I spot scams?', + a: "Never share your seed phrase. Admins NEVER DM first. Check you're on pancakeswap.finance. Ignore 'free CAKE' giveaways and wallet 'validation' requests.", + cat: 'Security', + icon: 'πŸ›‘οΈ', + seg: ['new', 'trader', 'earner'], + doc: 'https://docs.pancakeswap.finance/welcome-to-pancakeswap/contact-us/faq', + shill: null, + }, + { + id: 15, + q: 'What is CAKE.PAD?', + a: 'Replaced IFOs. Use CAKE for early access to new token launches. Participation fees are burned β€” every launch makes CAKE scarcer.', + cat: 'Product', + icon: 'πŸŽͺ', + seg: ['trader', 'earner'], + doc: 'https://docs.pancakeswap.finance/', + shill: { t: 'See launches β†’', l: 'https://pancakeswap.finance/' }, + }, + { + id: 16, + q: 'Why did my transaction fail?', + a: "Common causes: expired (confirm faster), gas too low (increase gas limit), network congestion (wait and retry). Don't panic β€” your funds are safe in your wallet.", + cat: 'Troubleshooting', + icon: '⏳', + seg: ['new', 'trader'], + doc: 'https://docs.pancakeswap.finance/welcome-to-pancakeswap/contact-us/faq/troubleshooting', + shill: null, + }, + { + id: 17, + q: 'How do yield farms work?', + a: 'Deposit LP tokens into farms to earn CAKE rewards on top of trading fees. Higher APR = more volatile. Always check if the farm pair matches your risk tolerance.', + cat: 'Earning', + icon: '🌾', + seg: ['earner'], + doc: 'https://docs.pancakeswap.finance/products/yield-farming', + shill: { t: 'Browse farms β†’', l: 'https://pancakeswap.finance/farms' }, + }, + { + id: 18, + q: 'Is PancakeSwap safe?', + a: 'Non-custodial (your keys, your crypto). Multiple audits (Infinity, V3, MasterChef). Multisig contracts with timelocks. Bug bounty program. But always DYOR on individual tokens.', + cat: 'Security', + icon: 'πŸ”’', + seg: ['new'], + doc: 'https://docs.pancakeswap.finance/welcome-to-pancakeswap/contact-us/faq', + shill: null, + }, +] + +interface PersonaInfo { + id: Persona + emoji: string + name: string + desc: string + colorKey: 'warning' | 'primary' | 'secondary' + menuName: string + menuDesc: string +} + +const PERSONAS: PersonaInfo[] = [ + { + id: 'new', + emoji: '🐣', + name: "I'm new here", + desc: 'Wallets, first swap, staying safe', + colorKey: 'warning', + menuName: 'Starter menu', + menuDesc: 'The essentials to get you going', + }, + { + id: 'trader', + emoji: 'πŸš€', + name: "I'm here to trade", + desc: 'Fees, slippage, multichain, perps', + colorKey: 'primary', + menuName: "Trader's specials", + menuDesc: 'Everything for the active swapper', + }, + { + id: 'earner', + emoji: '🍯', + name: 'I want to earn', + desc: 'Liquidity, farming, staking CAKE', + colorKey: 'secondary', + menuName: "Chef's earning menu", + menuDesc: 'Put your crypto to work', + }, +] + +const genTicket = () => + `PCS-${Date.now().toString(36).toUpperCase()}-${Math.random().toString(36).substring(2, 6).toUpperCase()}` + +// ---------- Bunny avatar (small inline drawing for chat header) ---------- + +const Bunny = ({ size = 40 }: { size?: number }) => ( + + + + + + + + + + + + + + + + +) + +// ---------- Pancake rain (cosmetic only) ---------- + +const PancakeRainCanvas = styled.canvas` + position: fixed; + inset: 0; + z-index: 9999; + pointer-events: none; +` + +const usePancakeRain = () => { + const canvasRef = useRef(null) + const pancakesRef = useRef< + { x: number; y: number; vx: number; vy: number; r: number; rs: number; s: number; o: number }[] + >([]) + + useEffect(() => { + const canvas = canvasRef.current + if (!canvas) return undefined + const ctx = canvas.getContext('2d') + if (!ctx) return undefined + const resize = () => { + canvas.width = window.innerWidth + canvas.height = window.innerHeight + } + resize() + window.addEventListener('resize', resize) + let raf = 0 + const loop = () => { + ctx.clearRect(0, 0, canvas.width, canvas.height) + pancakesRef.current.forEach((p) => { + // eslint-disable-next-line no-param-reassign + p.x += p.vx + // eslint-disable-next-line no-param-reassign + p.vy += 0.35 + // eslint-disable-next-line no-param-reassign + p.y += p.vy + // eslint-disable-next-line no-param-reassign + p.r += p.rs + if (p.y > canvas.height - 100) { + // eslint-disable-next-line no-param-reassign + p.o -= 0.03 + } + ctx.save() + ctx.translate(p.x, p.y) + ctx.rotate((p.r * Math.PI) / 180) + ctx.globalAlpha = p.o + ctx.beginPath() + ctx.ellipse(0, 2, p.s, p.s * 0.38, 0, 0, Math.PI * 2) + ctx.fillStyle = '#D1884F' + ctx.fill() + ctx.beginPath() + ctx.ellipse(0, -1, p.s, p.s * 0.38, 0, 0, Math.PI * 2) + ctx.fillStyle = '#FEDC90' + ctx.fill() + ctx.restore() + }) + pancakesRef.current = pancakesRef.current.filter((p) => p.o > 0 && p.y < canvas.height + 50) + raf = requestAnimationFrame(loop) + } + raf = requestAnimationFrame(loop) + return () => { + cancelAnimationFrame(raf) + window.removeEventListener('resize', resize) + } + }, []) + + const spawn = (x: number, y: number, n = 10) => { + for (let i = 0; i < n; i += 1) { + pancakesRef.current.push({ + x: x + (Math.random() - 0.5) * 80, + y: y - 10, + vx: (Math.random() - 0.5) * 5, + vy: -(Math.random() * 7 + 3), + r: Math.random() * 360, + rs: (Math.random() - 0.5) * 10, + s: 12 + Math.random() * 16, + o: 1, + }) + } + } + + return { canvasRef, spawn } +} + +// ---------- Styled helpers ---------- + +const Nav = styled.nav` + position: sticky; + top: 0; + z-index: 50; + background: ${({ theme }) => theme.colors.backgroundAlt}; + backdrop-filter: blur(16px); + border-bottom: 1px solid ${({ theme }) => theme.colors.cardBorder}; + padding: 0 20px; +` + +const NavInner = styled.div` + max-width: 1100px; + margin: 0 auto; + display: flex; + align-items: center; + justify-content: space-between; + height: 60px; +` + +const Hero = styled.div` + text-align: center; + padding: 56px 20px 28px; + max-width: 740px; + margin: 0 auto; +` + +const ShimmerHeading = styled(Heading)` + display: inline; + background: linear-gradient( + 90deg, + ${({ theme }) => theme.colors.primary}, + ${({ theme }) => theme.colors.secondary}, + ${({ theme }) => theme.colors.failure} + ); + background-size: 300% 100%; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + animation: shimmer 4s ease infinite; + @keyframes shimmer { + 0% { + background-position: 0% 50%; + } + 50% { + background-position: 100% 50%; + } + 100% { + background-position: 0% 50%; + } + } +` + +const PersonaCard = styled.div<{ accent: string }>` + background: ${({ theme }) => theme.colors.backgroundAlt}; + border: 2px solid ${({ theme }) => theme.colors.cardBorder}; + border-radius: 20px; + padding: 28px 24px; + cursor: pointer; + transition: all 0.3s; + position: relative; + overflow: hidden; + &:hover { + border-color: ${({ accent }) => accent}; + } +` + +const MenuBanner = styled(Flex)` + background: var(--pcs-colors-card-header); + border-radius: 20px; + padding: 24px 28px; + margin-bottom: 28px; + align-items: center; + gap: 18px; + flex-wrap: wrap; +` + +const FAQItemCard = styled(Card)<{ $open: boolean }>` + cursor: pointer; + border: 2px solid ${({ theme, $open }) => ($open ? theme.colors.primary : theme.colors.cardBorder)}; + transition: all 0.3s; +` + +const Chevron = styled(ChevronDownIcon)<{ $open: boolean }>` + transition: transform 0.3s; + transform: ${({ $open }) => ($open ? 'rotate(180deg)' : 'rotate(0)')}; +` + +const FilterScroller = styled(Flex)` + gap: 6px; + overflow-x: auto; + padding-bottom: 14px; + scrollbar-width: none; + &::-webkit-scrollbar { + display: none; + } +` + +const PillButton = styled(Button)` + white-space: nowrap; + flex-shrink: 0; +` + +const FabButton = styled(IconButton)<{ $open: boolean }>` + position: fixed; + bottom: 24px; + left: 24px; + z-index: 200; + width: 60px; + height: 60px; + border-radius: 50%; + border: 3px solid ${({ theme, $open }) => ($open ? theme.colors.failure : theme.colors.primary)}; + background: ${({ theme }) => theme.colors.backgroundAlt}; + box-shadow: 0 4px 24px rgba(31, 199, 212, 0.25); + animation: ${({ $open }) => ($open ? 'none' : 'fabPulse 3s ease infinite')}; + @keyframes fabPulse { + 0%, + 100% { + box-shadow: 0 4px 24px rgba(31, 199, 212, 0.25); + } + 50% { + box-shadow: 0 4px 32px rgba(31, 199, 212, 0.5), 0 0 0 8px rgba(31, 199, 212, 0.06); + } + } +` + +const ChatPanel = styled.div<{ $open: boolean }>` + position: fixed; + bottom: 96px; + left: 24px; + z-index: 199; + width: 390px; + max-width: calc(100vw - 48px); + height: 540px; + max-height: calc(100vh - 130px); + background: ${({ theme }) => theme.colors.backgroundAlt}; + border: 2px solid ${({ theme }) => theme.colors.cardBorder}; + border-radius: 22px; + display: flex; + flex-direction: column; + overflow: hidden; + opacity: ${({ $open }) => ($open ? 1 : 0)}; + transform: ${({ $open }) => ($open ? 'translateY(0) scale(1)' : 'translateY(20px) scale(0.95)')}; + pointer-events: ${({ $open }) => ($open ? 'all' : 'none')}; + transition: all 0.35s cubic-bezier(0.34, 1.56, 0.64, 1); +` + +const ChatHeader = styled(Flex)` + padding: 12px 16px; + border-bottom: 1px solid ${({ theme }) => theme.colors.cardBorder}; + align-items: center; + gap: 10px; + background: var(--pcs-colors-card-header); +` + +const ChatBubble = styled.div<{ $assistant: boolean }>` + max-width: 82%; + padding: 10px 14px; + border-radius: 16px; + font-size: 13px; + line-height: 1.55; + white-space: pre-wrap; + background: ${({ theme, $assistant }) => ($assistant ? theme.colors.input : theme.colors.primary)}; + color: ${({ theme, $assistant }) => ($assistant ? theme.colors.text : theme.colors.background)}; + border-bottom-left-radius: ${({ $assistant }) => ($assistant ? '4px' : '16px')}; + border-bottom-right-radius: ${({ $assistant }) => ($assistant ? '16px' : '4px')}; + align-self: ${({ $assistant }) => ($assistant ? 'flex-start' : 'flex-end')}; + font-weight: ${({ $assistant }) => ($assistant ? 400 : 500)}; +` + +const TypingDots = styled.div` + display: flex; + gap: 4px; + padding: 12px 16px; + background: ${({ theme }) => theme.colors.input}; + border-radius: 16px; + border-bottom-left-radius: 4px; + width: fit-content; + span { + width: 6px; + height: 6px; + border-radius: 50%; + background: ${({ theme }) => theme.colors.textDisabled}; + animation: typeDot 1.4s ease infinite; + } + span:nth-child(2) { + animation-delay: 0.2s; + } + span:nth-child(3) { + animation-delay: 0.4s; + } + @keyframes typeDot { + 0%, + 60%, + 100% { + transform: translateY(0); + opacity: 0.4; + } + 30% { + transform: translateY(-6px); + opacity: 1; + } + } +` + +const TicketIdBox = styled(Box)` + background: ${({ theme }) => theme.colors.input}; + border-radius: 12px; + padding: 12px 16px; + margin-bottom: 14px; +` + +// ---------- Inline brand marks ---------- + +const LogoMark: React.FC<{ height?: number }> = ({ height = 28 }) => { + const width = (451 / 47) * height + return ( + + + + + + + + + ) +} + +const BunnyMark: React.FC<{ size?: number }> = ({ size = 32 }) => ( + + + + + + + + + +) + +// ---------- Page ---------- + +interface ChatMessage { + role: 'user' | 'assistant' + text: string +} + +const cannedReply = (q: string): string => { + const lower = q.toLowerCase() + if (lower.includes('swap')) + return 'Easy! Connect your wallet, pick your tokens, enter an amount, and hit Swap. Want me to walk you through it step-by-step, fren?' + if (lower.includes('fee')) + return 'V2 is a flat 0.25%, V3 has flexible tiers from 0.01% to 1%. BNB Chain gas is pennies. No frontend surcharge.' + if (lower.includes('stake') || lower.includes('cake')) + return 'Head to Syrup Pools, stake CAKE, earn CAKE (or partner tokens). Flexible lets you withdraw anytime; Fixed gives higher APR but locks for a period.' + if (lower.includes('fail')) + return 'Most common cause is slippage too low. Bump it to 1-3% in settings. If it still fails, try a smaller amount or wait for less congestion.' + return "Great question! This is a Storybook demo β€” in production the Chef Bot would answer via the live Groq API. Try 'create ticket' to see the support flow." +} + +const TicketModalContent: React.FC<{ ticketId: string; onDismiss?: () => void }> = ({ ticketId, onDismiss }) => { + const theme = useTheme() + return ( + + + + 🎫 + + + + Ticket ID + + + {ticketId} + + + + Conversation logged. Reference this on PancakeSwap Discord for follow-up. + + + + + ) +} + +const FAQKitchen: React.FC = () => { + const theme = useTheme() + const [persona, setPersona] = useState(null) + const [activeCat, setActiveCat] = useState('All') + const [searchQ, setSearchQ] = useState('') + const [openFaq, setOpenFaq] = useState(null) + const [chatOpen, setChatOpen] = useState(false) + const [msgs, setMsgs] = useState([ + { + role: 'assistant', + text: "Hey fren! πŸ‘‹ I'm the PancakeSwap Chef Bot. Ask me anything β€” or describe your issue. If I can't fix it, I'll whip up a support ticket for you!", + }, + ]) + const [chatIn, setChatIn] = useState('') + const [typing, setTyping] = useState(false) + const [ticketId, setTicketId] = useState(null) + const chatEnd = useRef(null) + const { canvasRef, spawn } = usePancakeRain() + + const [onPresentTicket] = useModal(, true, true, 'ticketModal') + + useEffect(() => { + if (ticketId) onPresentTicket() + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [ticketId]) + + const readFaq = openFaq ? FAQS.find((f) => f.id === openFaq) ?? null : null + + const filtered = useMemo(() => { + return FAQS.filter((f) => { + if (persona && persona !== 'all' && !f.seg.includes(persona)) return false + if (activeCat !== 'All' && f.cat !== activeCat) return false + if (searchQ && !`${f.q}${f.a}${f.cat}`.toLowerCase().includes(searchQ.toLowerCase())) return false + return true + }) + }, [persona, activeCat, searchQ]) + const cats = useMemo(() => ['All', ...Array.from(new Set(filtered.map((f) => f.cat)))], [filtered]) + + useEffect(() => { + chatEnd.current?.scrollIntoView({ behavior: 'smooth' }) + }, [msgs, typing]) + + const personaInfo = PERSONAS.find((p) => p.id === persona) + + const send = (raw?: string) => { + const t = (raw ?? chatIn).trim() + if (!t || typing) return + setChatIn('') + setMsgs((p) => [...p, { role: 'user', text: t }]) + if (t.toLowerCase().includes('create ticket') || t.toLowerCase().includes('generate ticket')) { + setTyping(true) + window.setTimeout(() => { + setTyping(false) + const tid = genTicket() + setTicketId(tid) + const m = `🎫 Order up! Ticket created.\n\nTicket: ${tid}\nStatus: In the queue\n\nThe kitchen team will review. Use this ID on Discord for follow-up!` + setMsgs((p) => [...p, { role: 'assistant', text: m }]) + }, 900) + return + } + setTyping(true) + window.setTimeout(() => { + setTyping(false) + setMsgs((p) => [...p, { role: 'assistant', text: cannedReply(t) }]) + }, 900) + } + + return ( + + + + {/* === NAV === */} + + + {/* === HERO === */} + + + Welcome to the{' '} + + PancakeSwap Kitchen + + + + Got a question? Our Chef Bot has answers. Or browse the menu below. + + + + + + + setChatIn(e.target.value)} + onKeyDown={(e: React.KeyboardEvent) => { + if (e.key === 'Enter' && chatIn.trim()) { + setChatOpen(true) + send(chatIn) + } + }} + placeholder="Ask the Chef anything..." + style={{ paddingLeft: 50, height: 52, fontSize: 15, borderRadius: 20 }} + /> + + + {['How do I swap?', 'What are the fees?', 'Fix failed swap', 'Stake CAKE'].map((q) => ( + + ))} + + + + {/* === PERSONA PICKER === */} + {!persona && ( + + + + Choose your menu + + What brings you to the kitchen? + + + {PERSONAS.map((p) => { + const accent = theme.colors[p.colorKey] + return ( + { + setPersona(p.id) + setActiveCat('All') + setOpenFaq(null) + spawn(e.clientX, e.clientY, 20) + }} + > + + {p.emoji} + + + {p.name} + + + {p.desc} + + + Open {p.menuName.toLowerCase()} β†’ + + + ) + })} + + + + + + )} + + {/* === MENU / FAQ === */} + {persona && ( + + {personaInfo && ( + + {personaInfo.emoji} + + + Now serving + + + {personaInfo.menuName} + + + {personaInfo.menuDesc} + + + + + )} + + {persona === 'all' && !personaInfo && ( + + + + Now serving + + + πŸ₯ž The full menu + + + + + )} + + {/* Search + filters */} + + setSearchQ(e.target.value)} + placeholder="Search this menu..." + style={{ paddingLeft: 40 }} + /> + + + + + + + {cats.map((c) => ( + { + setActiveCat(c) + spawn(e.clientX, e.clientY, 5) + }} + > + {c === 'All' ? 'πŸ₯ž All dishes' : c} + + ))} + + + {/* Chef's recommendation */} + {readFaq?.shill && ( + + + πŸ’‘ Chef's recommendation + + + {readFaq.shill.t} + + + )} + + {/* FAQ items */} + + {filtered.map((f) => { + const open = openFaq === f.id + return ( + { + setOpenFaq(open ? null : f.id) + spawn(e.clientX, e.clientY, 4) + }} + > + + + + {f.icon} + + + {f.q} + + + + {open && ( + e.stopPropagation()} + > + + {f.a} + + + + {f.shill && ( + + )} + + + + )} + + + ) + })} + + + {filtered.length === 0 && ( + + + πŸ€” + + + Nothing on the menu for that + + + Try a different search or ask the Chef! + + + )} + + + + Still hungry for answers? + + + + + )} + + {/* === CHAT FAB === */} + { + setChatOpen((c) => !c) + spawn(e.clientX, e.clientY, 8) + }} + > + + + + {/* === CHAT PANEL === */} + + + + + + PancakeSwap Chef Bot + + + + + Always cooking + + + + setChatOpen(false)}> + + + + + + {msgs.map((m, i) => ( + + {m.text} + + ))} + {typing && ( + + + + + + )} +
+ + + {readFaq && ( + + + πŸ’‘ Reading:{' '} + + {readFaq.q} + + + + )} + + + setChatIn(e.target.value)} + onKeyDown={(e: React.KeyboardEvent) => { + if (e.key === 'Enter') send() + }} + placeholder="Ask the Chef..." + style={{ borderRadius: 100 }} + /> + send()} + disabled={typing} + style={{ borderRadius: '50%', flexShrink: 0 }} + > + ➀ + + + + + ) +} + +export const FAQKitchenPage: React.FC = () => ( + + + +) + +export const FAQTagShowcase: React.FC = () => ( + + + Category tags (Tag component) + + + {Array.from(new Set(FAQS.map((f) => f.cat))).map((c) => ( + + {c} + + ))} + + +)