chore: preload nested deps

This commit is contained in:
Aslam H
2024-11-13 22:27:43 +07:00
parent 7eda38955e
commit a44cf910b2
23 changed files with 231 additions and 234 deletions

View File

@@ -14,8 +14,6 @@ export function DefaultCatchBoundary({ error }: ErrorComponentProps) {
select: (state) => state.id === rootRouteId, select: (state) => state.id === rootRouteId,
}) })
console.error(error)
return ( return (
<div className="flex min-w-0 flex-1 flex-col items-center justify-center gap-6 p-4"> <div className="flex min-w-0 flex-1 flex-col items-center justify-center gap-6 p-4">
<ErrorComponent error={error} /> <ErrorComponent error={error} />

View File

@@ -45,7 +45,7 @@ export function KeyboardHandlerContent() {
const { signOut } = useAuth() const { signOut } = useAuth()
const navigate = useNavigate() const navigate = useNavigate()
const { me } = useAccountOrGuest() const { me } = useAccountOrGuest()
const { newPage } = usePageActions() const { createNewPage } = usePageActions()
const resetSequence = React.useCallback(() => { const resetSequence = React.useCallback(() => {
setSequence([]) setSequence([])
@@ -81,9 +81,9 @@ export function KeyboardHandlerContent() {
return return
} }
newPage() createNewPage()
}, },
[newPage], [createNewPage],
) )
useKeyDown( useKeyDown(

View File

@@ -3,7 +3,16 @@ import { useAccount } from "@/lib/providers/jazz-provider"
import { NavItem } from "~/components/custom/nav-item" import { NavItem } from "~/components/custom/nav-item"
export const LinkCollection: React.FC = () => { export const LinkCollection: React.FC = () => {
const { me } = useAccount() const { me } = useAccount({
root: {
personalLinks: [{}],
personalPages: [{}],
tasks: [{}],
topicsWantToLearn: [{}],
topicsLearning: [{}],
topicsLearned: [{}],
},
})
const linkCount = me?.root?.personalLinks?.length || 0 const linkCount = me?.root?.personalLinks?.length || 0
const pageCount = me?.root?.personalPages?.length || 0 const pageCount = me?.root?.personalPages?.length || 0

View File

@@ -49,11 +49,7 @@ const isExpandedAtom = atomWithStorage("isPageSectionExpanded", true)
export const PageSection: React.FC = () => { export const PageSection: React.FC = () => {
const { me } = useAccount({ const { me } = useAccount({
root: { root: {
personalPages: [ personalPages: [{}],
{
topic: {},
},
],
}, },
}) })
@@ -81,9 +77,9 @@ export const PageSection: React.FC = () => {
/> />
{isExpanded && ( {isExpanded && (
<div className="flex flex-col gap-px"> <div className="flex flex-col gap-px">
{sortedPages.map((page) => ( {sortedPages.map((page, index) => (
<Link <Link
key={page.id} key={index}
to="/pages/$pageId" to="/pages/$pageId"
params={{ pageId: page.id }} params={{ pageId: page.id }}
className={cn( className={cn(
@@ -168,13 +164,13 @@ const PageSectionHeader: React.FC<PageSectionHeaderProps> = ({
) )
const NewPageButton: React.FC = () => { const NewPageButton: React.FC = () => {
const { newPage } = usePageActions() const { createNewPage } = usePageActions()
const handleClick = async (e: React.MouseEvent<HTMLButtonElement>) => { const handleClick = async (e: React.MouseEvent<HTMLButtonElement>) => {
e.preventDefault() e.preventDefault()
e.stopPropagation() e.stopPropagation()
newPage() createNewPage()
} }
return ( return (

View File

@@ -19,22 +19,16 @@ import { SignInButton, useAuth, useUser } from "@clerk/tanstack-start"
import { Link, useLocation } from "@tanstack/react-router" import { Link, useLocation } from "@tanstack/react-router"
import { ShortcutKey } from "@shared/minimal-tiptap/components/shortcut-key" import { ShortcutKey } from "@shared/minimal-tiptap/components/shortcut-key"
import { Feedback } from "./feedback" import { Feedback } from "./feedback"
import { useAccount } from "~/lib/providers/jazz-provider"
export const ProfileSection: React.FC = () => { export const ProfileSection: React.FC = () => {
const { user, isSignedIn } = useUser() const { user, isSignedIn } = useUser()
const { signOut } = useAuth()
const [menuOpen, setMenuOpen] = React.useState(false) const [menuOpen, setMenuOpen] = React.useState(false)
const { pathname } = useLocation() const { pathname } = useLocation()
const [, setShowShortcut] = useAtom(showShortcutAtom) const [, setShowShortcut] = useAtom(showShortcutAtom)
const { disableKeydown } = useKeyboardManager("profileSection") const { disableKeydown } = useKeyboardManager("profileSection")
const handleSignOut = () => {
signOut(() => {
window.location.replace("/")
})
}
React.useEffect(() => { React.useEffect(() => {
disableKeydown(menuOpen) disableKeydown(menuOpen)
}, [menuOpen, disableKeydown]) }, [menuOpen, disableKeydown])
@@ -59,7 +53,6 @@ export const ProfileSection: React.FC = () => {
user={user} user={user}
menuOpen={menuOpen} menuOpen={menuOpen}
setMenuOpen={setMenuOpen} setMenuOpen={setMenuOpen}
signOut={handleSignOut}
setShowShortcut={setShowShortcut} setShowShortcut={setShowShortcut}
/> />
<span className="flex flex-auto"></span> <span className="flex flex-auto"></span>
@@ -73,7 +66,6 @@ interface ProfileDropdownProps {
user: any user: any
menuOpen: boolean menuOpen: boolean
setMenuOpen: (open: boolean) => void setMenuOpen: (open: boolean) => void
signOut: () => void
setShowShortcut: (show: boolean) => void setShowShortcut: (show: boolean) => void
} }
@@ -81,7 +73,6 @@ const ProfileDropdown: React.FC<ProfileDropdownProps> = ({
user, user,
menuOpen, menuOpen,
setMenuOpen, setMenuOpen,
signOut,
setShowShortcut, setShowShortcut,
}) => ( }) => (
<div className="flex min-w-0"> <div className="flex min-w-0">
@@ -107,60 +98,71 @@ const ProfileDropdown: React.FC<ProfileDropdownProps> = ({
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent className="w-56" align="start" side="top"> <DropdownMenuContent className="w-56" align="start" side="top">
<DropdownMenuItems <DropdownMenuItems setShowShortcut={setShowShortcut} />
signOut={signOut}
setShowShortcut={setShowShortcut}
/>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
</div> </div>
) )
interface DropdownMenuItemsProps { interface DropdownMenuItemsProps {
signOut: () => void
setShowShortcut: (show: boolean) => void setShowShortcut: (show: boolean) => void
} }
const DropdownMenuItems: React.FC<DropdownMenuItemsProps> = ({ const DropdownMenuItems: React.FC<DropdownMenuItemsProps> = ({
signOut,
setShowShortcut, setShowShortcut,
}) => ( }) => {
<> const { signOut } = useAuth()
<MenuLink href="/profile" icon="CircleUser" text="My profile" /> const { logOut } = useAccount()
<DropdownMenuItem className="gap-2" onClick={() => setShowShortcut(true)}>
<LaIcon name="Keyboard" /> const handleSignOut = React.useCallback(async () => {
<span>Shortcut</span> try {
</DropdownMenuItem> logOut()
<MenuLink href="/onboarding" icon="LayoutList" text="Onboarding" /> signOut(() => {
<DropdownMenuSeparator /> window.location.href = "/"
<MenuLink })
href="https://docs.learn-anything.xyz/" } catch (error) {
icon="Sticker" console.error("Error signing out:", error)
text="Docs" }
/> }, [logOut, signOut])
<MenuLink
href="https://github.com/learn-anything/learn-anything" return (
icon="Github" <>
text="GitHub" <MenuLink href="/profile" icon="CircleUser" text="My profile" />
/> <DropdownMenuItem className="gap-2" onClick={() => setShowShortcut(true)}>
<MenuLink <LaIcon name="Keyboard" />
href="https://discord.com/invite/bxtD8x6aNF" <span>Shortcut</span>
icon={DiscordIcon} </DropdownMenuItem>
text="Discord" <MenuLink href="/onboarding" icon="LayoutList" text="Onboarding" />
iconClass="-ml-1" <DropdownMenuSeparator />
/> <MenuLink
<DropdownMenuSeparator /> href="https://docs.learn-anything.xyz/"
<DropdownMenuItem onClick={signOut}> icon="Sticker"
<div className="relative flex flex-1 cursor-pointer items-center gap-2"> text="Docs"
<LaIcon name="LogOut" /> />
<span>Log out</span> <MenuLink
<div className="absolute right-0"> href="https://github.com/learn-anything/learn-anything"
<ShortcutKey keys={["alt", "shift", "q"]} /> icon="Github"
text="GitHub"
/>
<MenuLink
href="https://discord.com/invite/bxtD8x6aNF"
icon={DiscordIcon}
text="Discord"
iconClass="-ml-1"
/>
<DropdownMenuSeparator />
<DropdownMenuItem onClick={handleSignOut}>
<div className="relative flex flex-1 cursor-pointer items-center gap-2">
<LaIcon name="LogOut" />
<span>Log out</span>
<div className="absolute right-0">
<ShortcutKey keys={["alt", "shift", "q"]} />
</div>
</div> </div>
</div> </DropdownMenuItem>
</DropdownMenuItem> </>
</> )
) }
interface MenuLinkProps { interface MenuLinkProps {
href: string href: string

View File

@@ -8,7 +8,7 @@ import { useNavigate } from "@tanstack/react-router"
export const useCommandActions = () => { export const useCommandActions = () => {
const { setTheme } = useTheme() const { setTheme } = useTheme()
const navigate = useNavigate() const navigate = useNavigate()
const { newPage } = usePageActions() const { createNewPage } = usePageActions()
const changeTheme = React.useCallback( const changeTheme = React.useCallback(
(theme: string) => { (theme: string) => {
@@ -39,6 +39,6 @@ export const useCommandActions = () => {
navigateTo, navigateTo,
openLinkInNewTab, openLinkInNewTab,
copyCurrentURL, copyCurrentURL,
createNewPage: newPage, createNewPage,
} }
} }

View File

@@ -1,60 +1,60 @@
import * as React from "react" import * as React from "react"
import { toast } from "sonner" import { toast } from "sonner"
import { LaAccount, PersonalPage } from "@/lib/schema" import { PersonalPage } from "@/lib/schema"
import { ID } from "jazz-tools" import { ID } from "jazz-tools"
import { useNavigate } from "@tanstack/react-router" import { useNavigate } from "@tanstack/react-router"
import { useAccountOrGuest } from "~/lib/providers/jazz-provider" import { useAccountOrGuest } from "~/lib/providers/jazz-provider"
export const usePageActions = () => { export const usePageActions = () => {
const { me } = useAccountOrGuest() const { me: account } = useAccountOrGuest()
const navigate = useNavigate() const navigate = useNavigate()
const newPage = React.useCallback(() => { const createNewPage = React.useCallback(async () => {
if (!me) return try {
if (me._type !== "Account") return const isValidAccount = account && account._type === "Account"
if (!isValidAccount) return
const page = PersonalPage.create( const page = PersonalPage.create(
{ public: false, createdAt: new Date(), updatedAt: new Date() }, {
{ owner: me }, public: false,
) topic: null,
createdAt: new Date(),
updatedAt: new Date(),
},
{ owner: account },
)
me.root?.personalPages?.push(page) account.root?.personalPages?.push(page)
navigate({ to: "/pages/$pageId", params: { pageId: page.id } }) navigate({
}, [me, navigate]) to: "/pages/$pageId",
params: { pageId: page.id },
replace: true,
})
} catch (error) {
console.error(error)
}
}, [account, navigate])
const deletePage = React.useCallback( const deletePage = React.useCallback(
(me: LaAccount, pageId: ID<PersonalPage>): void => { (pageId: ID<PersonalPage>): void => {
if (!me.root?.personalPages) return const isValidAccount = account && account._type === "Account"
if (!isValidAccount) return
const index = me.root.personalPages.findIndex( const found = account.root?.personalPages?.findIndex(
(item) => item?.id === pageId, (item) => item?.id === pageId,
) )
if (index === -1) {
toast.error("Page not found")
return
}
const page = me.root.personalPages[index] if (found !== undefined && found > -1) {
if (!page) { account.root?.personalPages?.splice(found, 1)
toast.error("Page data is invalid")
return
}
try {
me.root.personalPages.splice(index, 1)
toast.success("Page deleted", { toast.success("Page deleted", {
position: "bottom-right", description: "The page has been deleted",
description: `${page.title} has been deleted.`,
}) })
} catch (error) {
console.error("Failed to delete page", error)
toast.error("Failed to delete page")
} }
}, },
[], [account],
) )
return { newPage, deletePage } return { createNewPage, deletePage }
} }

View File

@@ -2,21 +2,17 @@ import { createRouter as createTanStackRouter } from "@tanstack/react-router"
import { routeTree } from "./routeTree.gen" import { routeTree } from "./routeTree.gen"
import { DefaultCatchBoundary } from "./components/DefaultCatchBoundary" import { DefaultCatchBoundary } from "./components/DefaultCatchBoundary"
import { NotFound } from "./components/NotFound" import { NotFound } from "./components/NotFound"
import { QueryClient } from "@tanstack/react-query"
import { routerWithQueryClient } from "@tanstack/react-router-with-query"
export function createRouter() { export function createRouter() {
const queryClient = new QueryClient() const router = createTanStackRouter({
const router = routerWithQueryClient( routeTree,
createTanStackRouter({ defaultPreload: "intent",
routeTree, defaultErrorComponent: DefaultCatchBoundary,
defaultPreload: "intent", defaultNotFoundComponent: () => <NotFound />,
defaultErrorComponent: DefaultCatchBoundary, context: {
defaultNotFoundComponent: () => <NotFound />, auth: undefined,
context: { queryClient, auth: undefined! }, },
}), })
queryClient,
)
return router return router
} }

View File

@@ -1,6 +1,5 @@
/// <reference types="vite/client" /> /// <reference types="vite/client" />
import { getAuth } from "@clerk/tanstack-start/server" import { getAuth } from "@clerk/tanstack-start/server"
import type { QueryClient } from "@tanstack/react-query"
import { import {
Outlet, Outlet,
ScrollRestoration, ScrollRestoration,
@@ -29,15 +28,6 @@ export const TanStackRouterDevtools =
})), })),
) )
export const ReactQueryDevtools =
process.env.NODE_ENV === "production"
? () => null
: React.lazy(() =>
import("@tanstack/react-query-devtools").then((d) => ({
default: d.ReactQueryDevtools,
})),
)
export const fetchClerkAuth = createServerFn("GET", async (_, ctx) => { export const fetchClerkAuth = createServerFn("GET", async (_, ctx) => {
const auth = await getAuth(ctx.request) const auth = await getAuth(ctx.request)
@@ -45,8 +35,7 @@ export const fetchClerkAuth = createServerFn("GET", async (_, ctx) => {
}) })
export const Route = createRootRouteWithContext<{ export const Route = createRootRouteWithContext<{
auth: { userId: string } auth?: ReturnType<typeof getAuth> | null
queryClient: QueryClient
}>()({ }>()({
meta: () => [ meta: () => [
{ {
@@ -86,13 +75,25 @@ export const Route = createRootRouteWithContext<{
{ rel: "manifest", href: "/site.webmanifest", color: "#fffff" }, { rel: "manifest", href: "/site.webmanifest", color: "#fffff" },
{ rel: "icon", href: "/favicon.ico" }, { rel: "icon", href: "/favicon.ico" },
], ],
beforeLoad: async ({ context }) => { beforeLoad: async (ctx) => {
if (context.auth) { try {
return { auth: context.auth } // Handle explicit null auth (logged out state)
} if (ctx.context.auth === null) {
return { auth: null }
}
const auth = await fetchClerkAuth() // Use existing auth if available
return { auth } if (ctx.context.auth) {
return { auth: ctx.context.auth }
}
// Fetch new auth state
const auth = await fetchClerkAuth()
return { auth }
} catch (error) {
console.error("Error in beforeLoad:", error)
return { auth: null }
}
}, },
errorComponent: (props) => { errorComponent: (props) => {
return ( return (
@@ -101,6 +102,11 @@ export const Route = createRootRouteWithContext<{
</RootDocument> </RootDocument>
) )
}, },
pendingComponent: () => (
<RootDocument>
<div>Loading...</div>
</RootDocument>
),
notFoundComponent: () => <NotFound />, notFoundComponent: () => <NotFound />,
component: RootComponent, component: RootComponent,
}) })
@@ -124,7 +130,6 @@ function RootDocument({ children }: { children: React.ReactNode }) {
<React.Suspense> <React.Suspense>
<TanStackRouterDevtools position="bottom-right" /> <TanStackRouterDevtools position="bottom-right" />
<ReactQueryDevtools buttonPosition="bottom-right" />
</React.Suspense> </React.Suspense>
<ScrollRestoration /> <ScrollRestoration />

View File

@@ -17,7 +17,7 @@ export const openPopoverForIdAtom = atom<string | null>(null)
export function TopicDetailComponent() { export function TopicDetailComponent() {
const params = useParams({ from: "/_layout/_pages/(topic)/$" }) const params = useParams({ from: "/_layout/_pages/(topic)/$" })
const { me } = useAccountOrGuest({ root: { personalLinks: [] } }) const { me } = useAccountOrGuest({ root: { personalLinks: [{}] } })
const topicID = React.useMemo( const topicID = React.useMemo(
() => () =>

View File

@@ -29,9 +29,9 @@ export const TopicDetailHeader = function TopicDetailHeader({
const isMobile = useMedia("(max-width: 770px)") const isMobile = useMedia("(max-width: 770px)")
const { me } = useAccountOrGuest({ const { me } = useAccountOrGuest({
root: { root: {
topicsWantToLearn: [], topicsWantToLearn: [{}],
topicsLearning: [], topicsLearning: [{}],
topicsLearned: [], topicsLearned: [{}],
}, },
}) })

View File

@@ -25,7 +25,7 @@ export function TopicDetailList({
activeIndex, activeIndex,
setActiveIndex, setActiveIndex,
}: TopicDetailListProps) { }: TopicDetailListProps) {
const { me } = useAccountOrGuest({ root: { personalLinks: [] } }) const { me } = useAccountOrGuest({ root: { personalLinks: [{}] } })
const personalLinks = const personalLinks =
!me || me._type === "Anonymous" ? undefined : me.root.personalLinks !me || me._type === "Anonymous" ? undefined : me.root.personalLinks

View File

@@ -2,12 +2,24 @@ import { createFileRoute, Outlet, redirect } from "@tanstack/react-router"
export const Route = createFileRoute("/_layout/_pages/_protected")({ export const Route = createFileRoute("/_layout/_pages/_protected")({
beforeLoad: async ({ context, location }) => { beforeLoad: async ({ context, location }) => {
if (!context?.auth?.userId) { // Add extra validation
if (!context || !context.auth) {
throw redirect({ throw redirect({
to: "/sign-in/$", to: "/sign-in/$",
search: { redirect_url: location.pathname }, search: { redirect_url: location.pathname },
}) })
} }
const auth = await context.auth
if (!auth?.userId) {
throw redirect({
to: "/sign-in/$",
search: { redirect_url: location.pathname },
})
}
return context
}, },
component: () => <Outlet />, component: () => <Outlet />,
}) })

View File

@@ -26,7 +26,7 @@ interface Question {
function CommunityTopicComponent() { function CommunityTopicComponent() {
const { topicName } = Route.useParams() const { topicName } = Route.useParams()
const { me } = useAccountOrGuest({ root: { personalLinks: [] } }) const { me } = useAccountOrGuest()
const topicID = useMemo( const topicID = useMemo(
() => me && Topic.findUnique({ topicName }, JAZZ_GLOBAL_GROUP_ID, me), () => me && Topic.findUnique({ topicName }, JAZZ_GLOBAL_GROUP_ID, me),
[topicName, me], [topicName, me],

View File

@@ -143,7 +143,7 @@ export const LinkForm: React.FC<LinkFormProps> = ({
const [isFetching, setIsFetching] = React.useState(false) const [isFetching, setIsFetching] = React.useState(false)
const [urlFetched, setUrlFetched] = React.useState<string | null>(null) const [urlFetched, setUrlFetched] = React.useState<string | null>(null)
const { me } = useAccount() const { me } = useAccount({ root: { personalLinks: [{}] } })
const selectedLink = useCoState(PersonalLink, personalLink?.id) const selectedLink = useCoState(PersonalLink, personalLink?.id)
const form = useForm<LinkFormValues>({ const form = useForm<LinkFormValues>({

View File

@@ -80,9 +80,9 @@ const StepItem = ({
function OnboardingComponent() { function OnboardingComponent() {
const { me } = useAccount({ const { me } = useAccount({
root: { root: {
personalPages: [], personalPages: [{}],
personalLinks: [], personalLinks: [{}],
topicsWantToLearn: [], topicsWantToLearn: [{}],
}, },
}) })

View File

@@ -1,9 +1,9 @@
import * as React from "react" import * as React from "react"
import { createFileRoute, useNavigate } from "@tanstack/react-router" import { createFileRoute, useNavigate } from "@tanstack/react-router"
import { ID } from "jazz-tools" import { ID } from "jazz-tools"
import { LaAccount, PersonalPage } from "@/lib/schema" import { PersonalPage } from "@/lib/schema"
import { Content, EditorContent, useEditor } from "@tiptap/react" import { Content, EditorContent, useEditor } from "@tiptap/react"
import { useAccount, useCoState } from "@/lib/providers/jazz-provider" import { useCoState } from "@/lib/providers/jazz-provider"
import { EditorView } from "@tiptap/pm/view" import { EditorView } from "@tiptap/pm/view"
import { Editor } from "@tiptap/core" import { Editor } from "@tiptap/core"
import { generateUniqueSlug } from "@/lib/utils" import { generateUniqueSlug } from "@/lib/utils"
@@ -29,17 +29,10 @@ const TITLE_PLACEHOLDER = "Untitled"
function PageDetailComponent() { function PageDetailComponent() {
const { pageId } = Route.useParams() const { pageId } = Route.useParams()
const { me } = useAccount({
root: {
personalPages: [
{
topic: {},
},
],
},
})
const isMobile = useMedia("(max-width: 770px)") const isMobile = useMedia("(max-width: 770px)")
const page = useCoState(PersonalPage, pageId as ID<PersonalPage>) const page = useCoState(PersonalPage, pageId as ID<PersonalPage>, {
topic: {},
})
const navigate = useNavigate() const navigate = useNavigate()
const { deletePage } = usePageActions() const { deletePage } = usePageActions()
@@ -55,13 +48,13 @@ function PageDetailComponent() {
confirmButton: { variant: "destructive" }, confirmButton: { variant: "destructive" },
}) })
if (result && me?.root.personalPages) { if (result) {
deletePage(me, pageId as ID<PersonalPage>) deletePage(pageId as ID<PersonalPage>)
navigate({ to: "/pages" }) navigate({ to: "/pages" })
} }
}, [confirm, deletePage, me, pageId, navigate]) }, [confirm, deletePage, pageId, navigate])
if (!page || !me) return null if (!page) return null
return ( return (
<div className="absolute inset-0 flex flex-row overflow-hidden"> <div className="absolute inset-0 flex flex-row overflow-hidden">
@@ -72,7 +65,7 @@ function PageDetailComponent() {
handleDelete={handleDelete} handleDelete={handleDelete}
isMobile={isMobile} isMobile={isMobile}
/> />
<DetailPageForm page={page} me={me} /> <DetailPageForm page={page} />
</div> </div>
{!isMobile && ( {!isMobile && (
@@ -132,29 +125,11 @@ const SidebarActions = ({
SidebarActions.displayName = "SidebarActions" SidebarActions.displayName = "SidebarActions"
const DetailPageForm = ({ const DetailPageForm = ({ page }: { page: PersonalPage }) => {
page,
me,
}: {
page: PersonalPage
me: LaAccount
}) => {
const titleEditorRef = React.useRef<Editor | null>(null) const titleEditorRef = React.useRef<Editor | null>(null)
const contentEditorRef = React.useRef<Editor | null>(null) const contentEditorRef = React.useRef<Editor | null>(null)
const [isInitialSync, setIsInitialSync] = React.useState(true) const [isInitialSync, setIsInitialSync] = React.useState(true)
// const { id: pageId, title: pageTitle } = page
// React.useEffect(() => {
// if (!pageId) return
// if (!pageTitle && titleEditorRef.current) {
// titleEditorRef.current.commands.focus()
// } else if (contentEditorRef.current) {
// contentEditorRef.current.commands.focus()
// }
// }, [pageId, pageTitle])
React.useEffect(() => { React.useEffect(() => {
if (!page) return if (!page) return
@@ -293,17 +268,25 @@ const DetailPageForm = ({
onUpdate: ({ editor }) => handleUpdateTitle(editor), onUpdate: ({ editor }) => handleUpdateTitle(editor),
}) })
const { content: pageContent, title: pageTitle } = page
const handleCreate = React.useCallback( const handleCreate = React.useCallback(
({ editor }: { editor: Editor }) => { ({ editor }: { editor: Editor }) => {
contentEditorRef.current = editor contentEditorRef.current = editor
if (page.content) { if (pageContent) {
editor.commands.setContent(page.content as Content) editor.commands.setContent(pageContent as Content)
} }
setIsInitialSync(false) setIsInitialSync(false)
if (!pageTitle && titleEditorRef.current) {
titleEditorRef.current.commands.focus()
} else if (contentEditorRef.current) {
contentEditorRef.current.commands.focus()
}
}, },
[page.content], [pageContent, pageTitle],
) )
return ( return (
@@ -312,6 +295,7 @@ const DetailPageForm = ({
<form className="flex shrink-0 flex-col"> <form className="flex shrink-0 flex-col">
<div className="mb-2 mt-8 py-1.5"> <div className="mb-2 mt-8 py-1.5">
<EditorContent <EditorContent
key={page.id}
editor={titleEditor} editor={titleEditor}
className="title-editor no-command grow cursor-text select-text text-2xl font-semibold leading-[calc(1.33333)] tracking-[-0.00625rem]" className="title-editor no-command grow cursor-text select-text text-2xl font-semibold leading-[calc(1.33333)] tracking-[-0.00625rem]"
/> />
@@ -319,8 +303,7 @@ const DetailPageForm = ({
<div className="flex flex-auto flex-col"> <div className="flex flex-auto flex-col">
<div className="relative flex h-full max-w-full grow flex-col items-stretch p-0"> <div className="relative flex h-full max-w-full grow flex-col items-stretch p-0">
<LaEditor <LaEditor
me={me} key={page.id}
personalPage={page}
editorClassName="-mx-3.5 px-3.5 py-2.5 flex-auto focus:outline-none" editorClassName="-mx-3.5 px-3.5 py-2.5 flex-auto focus:outline-none"
value={page.content as Content} value={page.content as Content}
placeholder="Add content..." placeholder="Add content..."

View File

@@ -5,16 +5,12 @@ import {
SidebarToggleButton, SidebarToggleButton,
} from "@/components/custom/content-header" } from "@/components/custom/content-header"
import { LaIcon } from "@/components/custom/la-icon" import { LaIcon } from "@/components/custom/la-icon"
import { useAccount } from "@/lib/providers/jazz-provider"
import { usePageActions } from "~/hooks/actions/use-page-actions" import { usePageActions } from "~/hooks/actions/use-page-actions"
interface PageHeaderProps {} interface PageHeaderProps {}
export const PageHeader: React.FC<PageHeaderProps> = () => { export const PageHeader: React.FC<PageHeaderProps> = () => {
const { me } = useAccount() const { createNewPage } = usePageActions()
const { newPage } = usePageActions()
if (!me) return null
return ( return (
<ContentHeader> <ContentHeader>
@@ -28,7 +24,7 @@ export const PageHeader: React.FC<PageHeaderProps> = () => {
type="button" type="button"
variant="secondary" variant="secondary"
className="gap-x-2" className="gap-x-2"
onClick={newPage} onClick={createNewPage}
> >
<LaIcon name="Plus" /> <LaIcon name="Plus" />
<span className="hidden md:block">New page</span> <span className="hidden md:block">New page</span>

View File

@@ -26,7 +26,14 @@ const ProfileStats: React.FC<ProfileStatsProps> = ({ number, label }) => {
} }
function ProfileComponent() { function ProfileComponent() {
const account = useAccount() const account = useAccount({
profile: {},
root: {
topicsLearning: [{}],
topicsWantToLearn: [{}],
topicsLearned: [{}],
},
})
const username = "" const username = ""
const { user } = useUser() const { user } = useUser()
const avatarInputRef = React.useRef<HTMLInputElement>(null) const avatarInputRef = React.useRef<HTMLInputElement>(null)

View File

@@ -82,7 +82,7 @@ export const Route = createFileRoute("/_layout/_pages/_protected/tasks/")({
function TaskComponent() { function TaskComponent() {
const { filter } = Route.useSearch() const { filter } = Route.useSearch()
const { me } = useAccount({ root: { tasks: [] } }) const { me } = useAccount({ root: { tasks: [{}] } })
const tasks = me?.root.tasks const tasks = me?.root.tasks
const { deleteTask } = useTaskActions() const { deleteTask } = useTaskActions()

View File

@@ -30,7 +30,11 @@ export const TopicItem = React.forwardRef<HTMLAnchorElement, TopicItemProps>(
) )
const navigate = useNavigate() const navigate = useNavigate()
const { me } = useAccount({ const { me } = useAccount({
root: { topicsWantToLearn: [], topicsLearning: [], topicsLearned: [] }, root: {
topicsWantToLearn: [{}],
topicsLearning: [{}],
topicsLearned: [{}],
},
}) })
let p: { let p: {

View File

@@ -5,23 +5,13 @@ import { atom } from "jotai"
import { useMedia } from "@/hooks/use-media" import { useMedia } from "@/hooks/use-media"
import { useActiveItemScroll } from "@/hooks/use-active-item-scroll" import { useActiveItemScroll } from "@/hooks/use-active-item-scroll"
import { Column } from "@/components/custom/column" import { Column } from "@/components/custom/column"
import { LaAccount, ListOfTopics, Topic, UserRoot } from "@/lib/schema" import { Topic } from "@/lib/schema"
import { LearningStateValue } from "@/lib/constants" import { LearningStateValue } from "@/lib/constants"
import { useKeyDown } from "@/hooks/use-key-down" import { useKeyDown } from "@/hooks/use-key-down"
import { TopicItem } from "./-item" import { TopicItem } from "./-item"
interface TopicListProps {} interface TopicListProps {}
interface MainTopicListProps extends TopicListProps {
me: {
root: {
topicsWantToLearn: ListOfTopics
topicsLearning: ListOfTopics
topicsLearned: ListOfTopics
} & UserRoot
} & LaAccount
}
export interface PersonalTopic { export interface PersonalTopic {
topic: Topic | null topic: Topic | null
learningState: LearningStateValue learningState: LearningStateValue
@@ -31,15 +21,13 @@ export const topicOpenPopoverForIdAtom = atom<string | null>(null)
export const TopicList: React.FC<TopicListProps> = () => { export const TopicList: React.FC<TopicListProps> = () => {
const { me } = useAccount({ const { me } = useAccount({
root: { topicsWantToLearn: [], topicsLearning: [], topicsLearned: [] }, root: {
topicsWantToLearn: [{}],
topicsLearning: [{}],
topicsLearned: [{}],
},
}) })
if (!me) return null
return <MainTopicList me={me} />
}
export const MainTopicList: React.FC<MainTopicListProps> = ({ me }) => {
const isTablet = useMedia("(max-width: 640px)") const isTablet = useMedia("(max-width: 640px)")
const [activeItemIndex, setActiveItemIndex] = React.useState<number | null>( const [activeItemIndex, setActiveItemIndex] = React.useState<number | null>(
null, null,
@@ -49,21 +37,24 @@ export const MainTopicList: React.FC<MainTopicListProps> = ({ me }) => {
>(null) >(null)
const personalTopics = React.useMemo( const personalTopics = React.useMemo(
() => [ () =>
...me.root.topicsWantToLearn.map((topic) => ({ me
topic, ? [
learningState: "wantToLearn" as const, ...me.root.topicsWantToLearn.map((topic) => ({
})), topic,
...me.root.topicsLearning.map((topic) => ({ learningState: "wantToLearn" as const,
topic, })),
learningState: "learning" as const, ...me.root.topicsLearning.map((topic) => ({
})), topic,
...me.root.topicsLearned.map((topic) => ({ learningState: "learning" as const,
topic, })),
learningState: "learned" as const, ...me.root.topicsLearned.map((topic) => ({
})), topic,
], learningState: "learned" as const,
[me.root.topicsWantToLearn, me.root.topicsLearning, me.root.topicsLearned], })),
]
: [],
[me],
) )
const next = () => const next = () =>

View File

@@ -85,9 +85,7 @@ const SearchComponent = () => {
pages: PersonalPage[] pages: PersonalPage[]
}>({ topics: [], links: [], pages: [] }) }>({ topics: [], links: [], pages: [] })
const { me } = useAccountOrGuest({ const { me } = useAccountOrGuest()
root: { personalLinks: [{}], personalPages: [{}] },
})
const globalGroup = useCoState(PublicGlobalGroup, JAZZ_GLOBAL_GROUP_ID, { const globalGroup = useCoState(PublicGlobalGroup, JAZZ_GLOBAL_GROUP_ID, {
root: { root: {
@@ -112,14 +110,14 @@ const SearchComponent = () => {
links: links:
me?._type === "Anonymous" me?._type === "Anonymous"
? [] ? []
: me?.root.personalLinks?.filter( : me?.root?.personalLinks?.filter(
(link: PersonalLink | null): link is PersonalLink => (link: PersonalLink | null): link is PersonalLink =>
link !== null && link.title.toLowerCase().startsWith(value), link !== null && link.title.toLowerCase().startsWith(value),
) || [], ) || [],
pages: pages:
me?._type === "Anonymous" me?._type === "Anonymous"
? [] ? []
: me?.root.personalPages.filter( : me?.root?.personalPages?.filter(
(page): page is PersonalPage => (page): page is PersonalPage =>
page !== null && page !== null &&
page.title !== undefined && page.title !== undefined &&