-
-
Log out
-
-
+}) => {
+ const { signOut } = useAuth()
+ const { logOut } = useAccount()
+
+ const handleSignOut = React.useCallback(async () => {
+ try {
+ logOut()
+ signOut(() => {
+ window.location.href = "/"
+ })
+ } catch (error) {
+ console.error("Error signing out:", error)
+ }
+ }, [logOut, signOut])
+
+ return (
+ <>
+
+
setShowShortcut(true)}>
+
+ Shortcut
+
+
+
+
+
+
+
+
+
-
-
- >
-)
+
+ >
+ )
+}
interface MenuLinkProps {
href: string
diff --git a/web/app/hooks/actions/use-command-actions.ts b/web/app/hooks/actions/use-command-actions.ts
index 248a5012..1220ce15 100644
--- a/web/app/hooks/actions/use-command-actions.ts
+++ b/web/app/hooks/actions/use-command-actions.ts
@@ -8,7 +8,7 @@ import { useNavigate } from "@tanstack/react-router"
export const useCommandActions = () => {
const { setTheme } = useTheme()
const navigate = useNavigate()
- const { newPage } = usePageActions()
+ const { createNewPage } = usePageActions()
const changeTheme = React.useCallback(
(theme: string) => {
@@ -39,6 +39,6 @@ export const useCommandActions = () => {
navigateTo,
openLinkInNewTab,
copyCurrentURL,
- createNewPage: newPage,
+ createNewPage,
}
}
diff --git a/web/app/hooks/actions/use-page-actions.ts b/web/app/hooks/actions/use-page-actions.ts
index 49b09340..66a71eda 100644
--- a/web/app/hooks/actions/use-page-actions.ts
+++ b/web/app/hooks/actions/use-page-actions.ts
@@ -1,60 +1,60 @@
import * as React from "react"
import { toast } from "sonner"
-import { LaAccount, PersonalPage } from "@/lib/schema"
+import { PersonalPage } from "@/lib/schema"
import { ID } from "jazz-tools"
import { useNavigate } from "@tanstack/react-router"
import { useAccountOrGuest } from "~/lib/providers/jazz-provider"
export const usePageActions = () => {
- const { me } = useAccountOrGuest()
+ const { me: account } = useAccountOrGuest()
const navigate = useNavigate()
- const newPage = React.useCallback(() => {
- if (!me) return
- if (me._type !== "Account") return
+ const createNewPage = React.useCallback(async () => {
+ try {
+ const isValidAccount = account && account._type === "Account"
+ if (!isValidAccount) return
- const page = PersonalPage.create(
- { public: false, createdAt: new Date(), updatedAt: new Date() },
- { owner: me },
- )
+ const page = PersonalPage.create(
+ {
+ 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 } })
- }, [me, navigate])
+ navigate({
+ to: "/pages/$pageId",
+ params: { pageId: page.id },
+ replace: true,
+ })
+ } catch (error) {
+ console.error(error)
+ }
+ }, [account, navigate])
const deletePage = React.useCallback(
- (me: LaAccount, pageId: ID
): void => {
- if (!me.root?.personalPages) return
+ (pageId: ID): void => {
+ 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,
)
- if (index === -1) {
- toast.error("Page not found")
- return
- }
- const page = me.root.personalPages[index]
- if (!page) {
- toast.error("Page data is invalid")
- return
- }
-
- try {
- me.root.personalPages.splice(index, 1)
+ if (found !== undefined && found > -1) {
+ account.root?.personalPages?.splice(found, 1)
toast.success("Page deleted", {
- position: "bottom-right",
- description: `${page.title} has been deleted.`,
+ description: "The page 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 }
}
diff --git a/web/app/router.tsx b/web/app/router.tsx
index 959bf8b5..65975701 100644
--- a/web/app/router.tsx
+++ b/web/app/router.tsx
@@ -2,21 +2,17 @@ import { createRouter as createTanStackRouter } from "@tanstack/react-router"
import { routeTree } from "./routeTree.gen"
import { DefaultCatchBoundary } from "./components/DefaultCatchBoundary"
import { NotFound } from "./components/NotFound"
-import { QueryClient } from "@tanstack/react-query"
-import { routerWithQueryClient } from "@tanstack/react-router-with-query"
export function createRouter() {
- const queryClient = new QueryClient()
- const router = routerWithQueryClient(
- createTanStackRouter({
- routeTree,
- defaultPreload: "intent",
- defaultErrorComponent: DefaultCatchBoundary,
- defaultNotFoundComponent: () => ,
- context: { queryClient, auth: undefined! },
- }),
- queryClient,
- )
+ const router = createTanStackRouter({
+ routeTree,
+ defaultPreload: "intent",
+ defaultErrorComponent: DefaultCatchBoundary,
+ defaultNotFoundComponent: () => ,
+ context: {
+ auth: undefined,
+ },
+ })
return router
}
diff --git a/web/app/routes/__root.tsx b/web/app/routes/__root.tsx
index 2ed4809c..985eaa0e 100644
--- a/web/app/routes/__root.tsx
+++ b/web/app/routes/__root.tsx
@@ -1,6 +1,5 @@
///
import { getAuth } from "@clerk/tanstack-start/server"
-import type { QueryClient } from "@tanstack/react-query"
import {
Outlet,
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) => {
const auth = await getAuth(ctx.request)
@@ -45,8 +35,7 @@ export const fetchClerkAuth = createServerFn("GET", async (_, ctx) => {
})
export const Route = createRootRouteWithContext<{
- auth: { userId: string }
- queryClient: QueryClient
+ auth?: ReturnType | null
}>()({
meta: () => [
{
@@ -86,13 +75,25 @@ export const Route = createRootRouteWithContext<{
{ rel: "manifest", href: "/site.webmanifest", color: "#fffff" },
{ rel: "icon", href: "/favicon.ico" },
],
- beforeLoad: async ({ context }) => {
- if (context.auth) {
- return { auth: context.auth }
- }
+ beforeLoad: async (ctx) => {
+ try {
+ // Handle explicit null auth (logged out state)
+ if (ctx.context.auth === null) {
+ return { auth: null }
+ }
- const auth = await fetchClerkAuth()
- return { auth }
+ // Use existing auth if available
+ 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) => {
return (
@@ -101,6 +102,11 @@ export const Route = createRootRouteWithContext<{
)
},
+ pendingComponent: () => (
+
+ Loading...
+
+ ),
notFoundComponent: () => ,
component: RootComponent,
})
@@ -124,7 +130,6 @@ function RootDocument({ children }: { children: React.ReactNode }) {
-
diff --git a/web/app/routes/_layout/_pages/(topic)/$.tsx b/web/app/routes/_layout/_pages/(topic)/$.tsx
index b34a8aad..ba13cdbc 100644
--- a/web/app/routes/_layout/_pages/(topic)/$.tsx
+++ b/web/app/routes/_layout/_pages/(topic)/$.tsx
@@ -17,7 +17,7 @@ export const openPopoverForIdAtom = atom(null)
export function TopicDetailComponent() {
const params = useParams({ from: "/_layout/_pages/(topic)/$" })
- const { me } = useAccountOrGuest({ root: { personalLinks: [] } })
+ const { me } = useAccountOrGuest({ root: { personalLinks: [{}] } })
const topicID = React.useMemo(
() =>
diff --git a/web/app/routes/_layout/_pages/(topic)/-header.tsx b/web/app/routes/_layout/_pages/(topic)/-header.tsx
index 506a0ad3..56037083 100644
--- a/web/app/routes/_layout/_pages/(topic)/-header.tsx
+++ b/web/app/routes/_layout/_pages/(topic)/-header.tsx
@@ -29,9 +29,9 @@ export const TopicDetailHeader = function TopicDetailHeader({
const isMobile = useMedia("(max-width: 770px)")
const { me } = useAccountOrGuest({
root: {
- topicsWantToLearn: [],
- topicsLearning: [],
- topicsLearned: [],
+ topicsWantToLearn: [{}],
+ topicsLearning: [{}],
+ topicsLearned: [{}],
},
})
diff --git a/web/app/routes/_layout/_pages/(topic)/-list.tsx b/web/app/routes/_layout/_pages/(topic)/-list.tsx
index 35d6b875..b6d43e6b 100644
--- a/web/app/routes/_layout/_pages/(topic)/-list.tsx
+++ b/web/app/routes/_layout/_pages/(topic)/-list.tsx
@@ -25,7 +25,7 @@ export function TopicDetailList({
activeIndex,
setActiveIndex,
}: TopicDetailListProps) {
- const { me } = useAccountOrGuest({ root: { personalLinks: [] } })
+ const { me } = useAccountOrGuest({ root: { personalLinks: [{}] } })
const personalLinks =
!me || me._type === "Anonymous" ? undefined : me.root.personalLinks
diff --git a/web/app/routes/_layout/_pages/_protected.tsx b/web/app/routes/_layout/_pages/_protected.tsx
index e52efd3b..e6d63beb 100644
--- a/web/app/routes/_layout/_pages/_protected.tsx
+++ b/web/app/routes/_layout/_pages/_protected.tsx
@@ -2,12 +2,24 @@ import { createFileRoute, Outlet, redirect } from "@tanstack/react-router"
export const Route = createFileRoute("/_layout/_pages/_protected")({
beforeLoad: async ({ context, location }) => {
- if (!context?.auth?.userId) {
+ // Add extra validation
+ if (!context || !context.auth) {
throw redirect({
to: "/sign-in/$",
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: () => ,
})
diff --git a/web/app/routes/_layout/_pages/_protected/community/$topicName/index.tsx b/web/app/routes/_layout/_pages/_protected/community/$topicName/index.tsx
index 186e9354..44d58e06 100644
--- a/web/app/routes/_layout/_pages/_protected/community/$topicName/index.tsx
+++ b/web/app/routes/_layout/_pages/_protected/community/$topicName/index.tsx
@@ -26,7 +26,7 @@ interface Question {
function CommunityTopicComponent() {
const { topicName } = Route.useParams()
- const { me } = useAccountOrGuest({ root: { personalLinks: [] } })
+ const { me } = useAccountOrGuest()
const topicID = useMemo(
() => me && Topic.findUnique({ topicName }, JAZZ_GLOBAL_GROUP_ID, me),
[topicName, me],
diff --git a/web/app/routes/_layout/_pages/_protected/links/-link-form.tsx b/web/app/routes/_layout/_pages/_protected/links/-link-form.tsx
index 4150bc33..487d16bb 100644
--- a/web/app/routes/_layout/_pages/_protected/links/-link-form.tsx
+++ b/web/app/routes/_layout/_pages/_protected/links/-link-form.tsx
@@ -143,7 +143,7 @@ export const LinkForm: React.FC = ({
const [isFetching, setIsFetching] = React.useState(false)
const [urlFetched, setUrlFetched] = React.useState(null)
- const { me } = useAccount()
+ const { me } = useAccount({ root: { personalLinks: [{}] } })
const selectedLink = useCoState(PersonalLink, personalLink?.id)
const form = useForm({
diff --git a/web/app/routes/_layout/_pages/_protected/onboarding/index.tsx b/web/app/routes/_layout/_pages/_protected/onboarding/index.tsx
index ea4371a1..865c74cc 100644
--- a/web/app/routes/_layout/_pages/_protected/onboarding/index.tsx
+++ b/web/app/routes/_layout/_pages/_protected/onboarding/index.tsx
@@ -80,9 +80,9 @@ const StepItem = ({
function OnboardingComponent() {
const { me } = useAccount({
root: {
- personalPages: [],
- personalLinks: [],
- topicsWantToLearn: [],
+ personalPages: [{}],
+ personalLinks: [{}],
+ topicsWantToLearn: [{}],
},
})
diff --git a/web/app/routes/_layout/_pages/_protected/pages/$pageId/index.tsx b/web/app/routes/_layout/_pages/_protected/pages/$pageId/index.tsx
index bed1bba7..1b2a000b 100644
--- a/web/app/routes/_layout/_pages/_protected/pages/$pageId/index.tsx
+++ b/web/app/routes/_layout/_pages/_protected/pages/$pageId/index.tsx
@@ -1,9 +1,9 @@
import * as React from "react"
import { createFileRoute, useNavigate } from "@tanstack/react-router"
import { ID } from "jazz-tools"
-import { LaAccount, PersonalPage } from "@/lib/schema"
+import { PersonalPage } from "@/lib/schema"
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 { Editor } from "@tiptap/core"
import { generateUniqueSlug } from "@/lib/utils"
@@ -29,17 +29,10 @@ const TITLE_PLACEHOLDER = "Untitled"
function PageDetailComponent() {
const { pageId } = Route.useParams()
- const { me } = useAccount({
- root: {
- personalPages: [
- {
- topic: {},
- },
- ],
- },
- })
const isMobile = useMedia("(max-width: 770px)")
- const page = useCoState(PersonalPage, pageId as ID)
+ const page = useCoState(PersonalPage, pageId as ID, {
+ topic: {},
+ })
const navigate = useNavigate()
const { deletePage } = usePageActions()
@@ -55,13 +48,13 @@ function PageDetailComponent() {
confirmButton: { variant: "destructive" },
})
- if (result && me?.root.personalPages) {
- deletePage(me, pageId as ID)
+ if (result) {
+ deletePage(pageId as ID)
navigate({ to: "/pages" })
}
- }, [confirm, deletePage, me, pageId, navigate])
+ }, [confirm, deletePage, pageId, navigate])
- if (!page || !me) return null
+ if (!page) return null
return (
@@ -72,7 +65,7 @@ function PageDetailComponent() {
handleDelete={handleDelete}
isMobile={isMobile}
/>
-
+
{!isMobile && (
@@ -132,29 +125,11 @@ const SidebarActions = ({
SidebarActions.displayName = "SidebarActions"
-const DetailPageForm = ({
- page,
- me,
-}: {
- page: PersonalPage
- me: LaAccount
-}) => {
+const DetailPageForm = ({ page }: { page: PersonalPage }) => {
const titleEditorRef = React.useRef(null)
const contentEditorRef = React.useRef(null)
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(() => {
if (!page) return
@@ -293,17 +268,25 @@ const DetailPageForm = ({
onUpdate: ({ editor }) => handleUpdateTitle(editor),
})
+ const { content: pageContent, title: pageTitle } = page
+
const handleCreate = React.useCallback(
({ editor }: { editor: Editor }) => {
contentEditorRef.current = editor
- if (page.content) {
- editor.commands.setContent(page.content as Content)
+ if (pageContent) {
+ editor.commands.setContent(pageContent as Content)
}
setIsInitialSync(false)
+
+ if (!pageTitle && titleEditorRef.current) {
+ titleEditorRef.current.commands.focus()
+ } else if (contentEditorRef.current) {
+ contentEditorRef.current.commands.focus()
+ }
},
- [page.content],
+ [pageContent, pageTitle],
)
return (
@@ -312,6 +295,7 @@ const DetailPageForm = ({