import { AnimatePresence, motion } from "motion/react" import React, { useEffect, useLayoutEffect, useRef, useState } from "react" import { useFetcher } from "react-router" import { Resend } from "resend" import { Streamdown } from "streamdown" import { ChatBox } from "~/chat/chat-box" import { INITLAL_MESSAGES, waitListJoinedMessage, type Message, type SystemMessage, type UserMessage, } from "~/chat/message" import { useFakeStreaming } from "~/chat/use-fake-streaming" import { AnimatedLogo, AnimatedLogoState, AnimatedLogoState as TAnimatedLogoState, } from "~/components/animated-logo" import { ProgressiveBlur } from "~/components/progressive-blur" import type { Route } from "./+types/home" export function meta({}: Route.MetaArgs) { return [ { title: "New React Router App" }, { name: "description", content: "Welcome to React Router!" }, ] } export async function action({ request }: Route.ActionArgs) { const formData = await request.formData() const email = formData.get("email") if (typeof email !== "string" || !isValidEmail(email)) { return { error: "Invalid email" } } const resend = new Resend(process.env.RESEND_API_KEY) const res = await resend.contacts.create({ email, segments: [ { // resend segment id for "Waitlist" segment id: "b80fb036-74a1-4f7d-bca5-2c035b696071", }, ], }) if (res.error) { console.log("Error adding contact to Resend:", res.error) return { error: res.error.message } } return { email } } export default function Home() { const [messages, setMessages] = useState(INITLAL_MESSAGES) const [emailSent, setEmailSent] = useState("") const [isAnimatingSend, setIsAnimatingSend] = useState(false) const [logoState, setLogoState] = useState(AnimatedLogoState.Idle) const chatBoxRef = useRef(null) const fetcher = useFetcher() useEffect(() => { if (fetcher.data?.email && !isAnimatingSend) { setMessages((messages) => [...messages, waitListJoinedMessage(fetcher.data.email)]) } else if (fetcher.data?.error) { console.error(fetcher.data.error) } }, [fetcher.data?.email, fetcher.data?.error, isAnimatingSend]) const insertEmailMessage = (email: string) => { setEmailSent(email) setIsAnimatingSend(true) setLogoState(AnimatedLogoState.Loading) setMessages((messages) => [ ...messages, { role: "user", message: email, bubbleLayoutId: "test", }, ]) fetcher.submit({ email }, { method: "post" }) } let chatBox: React.ReactNode if (emailSent && isAnimatingSend) { const chatBoxRect = chatBoxRef.current?.getBoundingClientRect() const mainRect = chatBoxRef.current?.offsetParent?.getBoundingClientRect() chatBox = ( { setIsAnimatingSend(false) }} > {emailSent} ) } else if (!emailSent) { chatBox = ( {logoState === AnimatedLogoState.Idle && !emailSent && ( )} ) } else { chatBox = null } return (
{ setLogoState(AnimatedLogoState.Loading) }} onMessageStreamEnd={() => { setLogoState(AnimatedLogoState.Idle) }} /> {chatBox}
) } function MorphingChatBox({ chatBoxWidth, chatBoxHeight, chatBoxLeft, chatBoxTop, onAnimationEnd, children, }: React.PropsWithChildren<{ chatBoxWidth: number chatBoxHeight: number chatBoxLeft: number chatBoxTop: number onAnimationEnd: () => void }>) { const [targetWidth, setTargetWidth] = useState(-1) const [targetHeight, setTargetHeight] = useState(-1) const [targetCoords, setTargetCoords] = useState([0, 0]) useLayoutEffect(() => { const bubble = document.getElementById("test") if (bubble) { const mainRect = bubble.closest("main")?.getBoundingClientRect() const rect = bubble.getBoundingClientRect() setTargetWidth(bubble.offsetWidth) setTargetHeight(bubble.offsetHeight) setTargetCoords([rect.left - (mainRect?.left ?? 0), rect.top - (mainRect?.top ?? 0)]) } }, []) if (targetWidth < 0 || targetHeight < 0) { return null } return ( {children} ) } function MessageList({ messages, showLastMessage, onMessageStreamStart, onMessageStreamEnd, }: { messages: Message[] showLastMessage: boolean onMessageStreamStart: () => void onMessageStreamEnd: () => void }) { console.log({ messages, showLastMessage }) return (
    {messages.map((message, index) => (
  • ))}
) } function MessageContent({ message, onMessageStreamStart, onMessageStreamEnd, }: { message: Message onMessageStreamStart: () => void onMessageStreamEnd: () => void }) { switch (message.role) { case "user": return case "system": return ( ) } } function UserMessageBubble({ message }: { message: UserMessage }) { return (
{message.message}
) } function SystemMessageBubble({ message, onStreamStart, onStreamEnd, }: { message: SystemMessage onStreamStart: () => void onStreamEnd: () => void }) { const { currentContent, isStreaming } = useFakeStreaming(message.message) const ref = useRef(null) useEffect(() => { ref.current?.scrollIntoView({ behavior: "smooth", block: "end" }) }, [currentContent]) useEffect(() => { if (isStreaming) { onStreamStart() } else { onStreamEnd() } }, [isStreaming]) return ( ) } function isValidEmail(value: string): boolean { return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(value) }