Skip to content

Commit

Permalink
🎉 feat: better caching, transition
Browse files Browse the repository at this point in the history
  • Loading branch information
SaltyAom committed Dec 31, 2023
1 parent 9256755 commit b745108
Show file tree
Hide file tree
Showing 25 changed files with 353 additions and 118 deletions.
12 changes: 12 additions & 0 deletions apps/medium/next.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,18 @@ module.exports = withPlugins(
typescript: {
ignoreBuildErrors: true
},
webpack: (config) => {
config.module.rules.push({
test: /\.m?js$/,
type: 'javascript/auto',
resolve: {
fullySpecified: false
}
})

return config
},

async rewrites() {
return [
{
Expand Down
8 changes: 6 additions & 2 deletions apps/medium/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,24 @@
"@iconify/react": "^4.1.1",
"@radix-ui/react-slot": "^1.0.2",
"@tanstack/react-query": "^5.15.0",
"@tanstack/query-sync-storage-persister": "^5.14.2",
"@tanstack/react-query-persist-client": "^5.15.0",
"@vercel/og": "^0.5.20",
"class-variance-authority": "^0.7.0",
"clsx": "^2.0.0",
"elysia": "^0.8.6",
"jotai": "^2.6.0",
"framer-motion": "^10.16.16",
"jotai": "^2.6.1",
"lucide-react": "^0.302.0",
"nanoid": "^5.0.4",
"next": "^14.0.0",
"next": "^14.0.4",
"next-pwa": "^5.6.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-hook-form": "^7.49.2",
"react-markdown": "^9.0.1",
"react-textarea-resize": "^1.0.0",
"ui": "workspace:*",
"tailwind-merge": "^2.2.0",
"tailwindcss": "^3.3.5",
"tailwindcss-animate": "^1.0.7",
Expand Down
36 changes: 36 additions & 0 deletions apps/medium/src/app/(user)/chat/[id]/clean-up.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
'use client'

import { useEffect } from 'react'
import { useParams } from 'next/navigation'

import { useHydrateAtoms } from 'jotai/utils'

import { Composer, Chat, Conversation } from '@modules/chat'
import {
characterIdAtom,
useCharacterId,
useChat
} from '@components/modules/chat/store'

export default function CleanUp({
characterId = null
}: {
characterId?: string | null
}) {
const { id } = useParams()
const [char, setCharacterId] = useCharacterId()
const { dispatch } = useChat()

useHydrateAtoms([[characterIdAtom, characterId]])

useEffect(() => {
setCharacterId(Array.isArray(id) ? id[0] : id)

return () => {
setCharacterId(null)
dispatch({ type: 'set', payload: [] })
}
}, [id])

return null
}
65 changes: 45 additions & 20 deletions apps/medium/src/app/(user)/chat/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,32 +1,57 @@
'use client'

import { useEffect } from 'react'
import { useParams } from 'next/navigation'

import {
HydrationBoundary,
QueryClient,
dehydrate
} from '@tanstack/react-query'

import { queryClient } from '@app/providers'
import { Composer, Chat, Conversation } from '@modules/chat'
import { useCharacterId, useChat } from '@components/modules/chat/store'
import CleanUp from './clean-up'

import { isServer, resonator } from '@services'
import { useHydrateAtoms } from '@stores/jotai'
import { characterIdAtom } from '@components/modules/chat/store'

export default async function Chatroom({
params: { id }
}: {
params: { id: string }
}) {
const queryClient = new QueryClient({
defaultOptions: {
queries: {
refetchOnWindowFocus: false,
networkMode: 'offlineFirst'
}
}
})

export default function Chatroom() {
const { id } = useParams()
const [, setCharacterId] = useCharacterId()
const { dispatch } = useChat()
await queryClient.prefetchQuery({
queryKey: ['character', id],
async queryFn() {
const { data, error } = await resonator.character[id!].get()

useEffect(() => {
setCharacterId(Array.isArray(id) ? id[0] : id)
if (error) throw error

return () => {
setCharacterId(null)
dispatch({ type: 'set', payload: [] })
return data
}
}, [id])
})

return (
<section className="flex flex-col md:flex-row w-full min-h-screen">
<Conversation />
<main className="flex flex-col flex-1 max-w-2xl w-full mx-auto">
<Chat />
<Composer />
</main>
</section>
<>
<CleanUp characterId={id} />
<section className="flex flex-col md:flex-row w-full min-h-screen">
<HydrationBoundary state={dehydrate(queryClient)}>
<Conversation />
<main className="flex flex-col flex-1 max-w-2xl w-full mx-auto">
<Chat />
<Composer />
</main>
</HydrationBoundary>
</section>
</>
)
}
78 changes: 46 additions & 32 deletions apps/medium/src/app/(user)/chat/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
'use client'

import Link from 'next/link'
import { motion, AnimatePresence } from 'framer-motion'

import { useQuery } from '@tanstack/react-query'

Expand Down Expand Up @@ -31,39 +32,52 @@ export default function Chats() {
</Link>
</header>
<ul className="flex flex-col gap-1">
{isRoomLoading
? Array.from({ length: 8 }).map((_, i) => (
<a key={i.toString()}>
<article className="flex items-center gap-3 md:gap-4 w-full px-4 py-3 md:p-2 md:rounded-xl">
<div className="size-12 min-w-12 md:size-14 md:min-w-14 rounded-full bg-lime-100" />
<section className="flex flex-col w-full gap-2 overflow-hidden">
<h2 className="text-lime-800 text-xl w-48 h-6 md:h-8 bg-lime-100 rounded" />
<p className="text-lime-600 text-sm whitespace-nowrap text-ellipsis overflow-hidden w-full h-4 bg-lime-100 rounded" />
</section>
</article>
</a>
))
: rooms.map(
({ character: { id, name, greeting, image } }) => (
<Link href={`/chat/${id}`} key={id}>
<article className="flex items-center gap-3 md:gap-4 w-full px-4 py-3 md:p-2 sm:rounded-2xl hover:bg-lime-100 transition-colors">
<img
src={image ?? ''}
alt={name}
className="size-12 md:size-14 rounded-full object-center items-center"
/>
<section className="flex flex-col w-full overflow-hidden">
<h2 className="text-lime-800 text-xl md:text-2xl">
{name}
</h2>
<p className="text-lime-600 text-sm w-full whitespace-nowrap text-ellipsis overflow-hidden">
{greeting}
</p>
<AnimatePresence mode="popLayout">
{isRoomLoading
? Array.from({ length: 8 }).map((_, i) => (
<a key={i.toString()}>
<motion.article
className="flex items-center gap-3 md:gap-4 w-full px-4 py-3 md:p-2 md:rounded-xl"
exit={{
opacity: 0,
transition: {
duration: 0.2,
delay: i * 0.0125
}
}}
>
<div className="size-12 min-w-12 md:size-14 md:min-w-14 rounded-full bg-lime-100" />
<section className="flex flex-col w-full gap-2 overflow-hidden">
<h2 className="text-lime-800 text-xl w-48 h-6 md:h-8 bg-lime-100 rounded" />
<p className="text-lime-600 text-sm whitespace-nowrap text-ellipsis overflow-hidden w-full h-4 bg-lime-100 rounded" />
</section>
</article>
</Link>
)
)}
</motion.article>
</a>
))
: rooms.map(
({
character: { id, name, greeting, image }
}) => (
<Link href={`/chat/${id}`} key={id}>
<article className="flex items-center gap-3 md:gap-4 w-full px-4 py-3 md:p-2 sm:rounded-2xl hover:bg-lime-100 transition-colors">
<img
src={image ?? ''}
alt={name}
className="size-12 md:size-14 rounded-full object-center items-center"
/>
<section className="flex flex-col w-full overflow-hidden">
<h2 className="text-lime-800 text-xl md:text-2xl">
{name}
</h2>
<p className="text-lime-600 text-sm w-full whitespace-nowrap text-ellipsis overflow-hidden">
{greeting}
</p>
</section>
</article>
</Link>
)
)}
</AnimatePresence>
</ul>
</main>
)
Expand Down
2 changes: 1 addition & 1 deletion apps/medium/src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export default async function Index() {
<h1 className="text-3xl font-medium text-lime-800">Characters</h1>
<ul className="gallery my-4 gap-4">
{data?.map(({ id, name, greeting, image }) => (
<Link href={`/chat/${id}`} key={id}>
<Link href={`/chat/${id}`} key={id} className="touch-callout-none">
<li className="flex flex-col justify-center items-center gap-1 md:gap-2 mb-auto p-4 rounded-2xl hover:bg-lime-100 focus:bg-lime-100 transition-colors">
<figure className="size-24 object-center object-cover rounded-full mb-2 bg-lime-100 overflow-hidden">
<img
Expand Down
48 changes: 44 additions & 4 deletions apps/medium/src/app/providers.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
'use client'

import type { PropsWithChildren } from 'react'
import { Fragment, type PropsWithChildren } from 'react'

import { Provider as JotaiProvider } from 'jotai'
import { Provider as JotaiProvider, createStore } from 'jotai'

import {
QueryClientProvider,
Expand All @@ -14,6 +14,10 @@ import { persistQueryClient } from '@tanstack/react-query-persist-client'
import { createSyncStoragePersister } from '@tanstack/query-sync-storage-persister'

import { isServer } from '@services'
import { usePathname } from 'next/navigation'

import { motion, AnimatePresence } from 'framer-motion'
import { FrozenRouter } from './transition'

const persister = createSyncStoragePersister({
storage: isServer ? undefined : window.localStorage
Expand All @@ -28,16 +32,52 @@ export const queryClient = new QueryClient({
}
})

const jotaiStore = createStore()

export default function Provider({ children }: PropsWithChildren) {
const pathname = usePathname()

return (
<JotaiProvider>
<JotaiProvider store={jotaiStore}>
<PersistQueryClientProvider
client={queryClient}
persistOptions={{
persister
}}
>
{children}
<AnimatePresence initial={false}>
<motion.div
key={pathname}
className="page"
initial={{
left: '100%',
zIndex: 10,
overflow: 'hidden'
}}
animate={{
left: 0,
zIndex: 10,
overflow: 'unset',
transition: {
delay: 0.05
}
}}
exit={{
overflow: 'hidden',
left: '-30%',
transition: {
delay: 0.05
}
}}
transition={{
when: 'afterChildren',
duration: 0.4,
ease: [0.16, 1, 0.3, 1],
}}
>
<FrozenRouter>{children}</FrozenRouter>
</motion.div>
</AnimatePresence>
</PersistQueryClientProvider>
</JotaiProvider>
)
Expand Down
15 changes: 15 additions & 0 deletions apps/medium/src/app/transition.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
'use client'

import { useContext, useRef, PropsWithChildren } from 'react'
import { LayoutRouterContext } from 'next/dist/shared/lib/app-router-context.shared-runtime'

export function FrozenRouter(props: PropsWithChildren<{}>) {
const context = useContext(LayoutRouterContext)
const frozen = useRef(context).current

return (
<LayoutRouterContext.Provider value={frozen}>
{props.children}
</LayoutRouterContext.Provider>
)
}
11 changes: 11 additions & 0 deletions apps/medium/src/components/modules/chat/chat/chat.sass
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,17 @@
@apply size-9 min-w-9 md:size-11 md:min-w-11 rounded-full object-center object-cover select-none
-webkit-user-drag: none

&.-loading
animation: fade-in .2s ease-out .2s 1 normal backwards

@keyframes fade-in
0%
opacity: 0

100%
opacity: 1


& > div
@apply px-4 py-3 rounded-xl text-lime-800 bg-lime-100

Expand Down
Loading

0 comments on commit b745108

Please sign in to comment.