Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions components/entry/entry-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,11 @@ import {
restrictToParentElement
} from "@dnd-kit/modifiers";
import { CSS } from "@dnd-kit/utilities";
import { Blocker } from "@/components/navigation-block";
import { ChevronLeft, GripVertical, Loader, Plus, Trash2, Ellipsis } from "lucide-react";
import { useConfig } from "@/contexts/config-context";
import { toast } from "sonner";

const SortableItem = ({
id,
type,
Expand Down Expand Up @@ -532,6 +534,7 @@ const EntryForm = ({

return (
<Form {...form}>
{isDirty && <Blocker />}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure I understand the conditional on <Blocker />. When would <Bocker /> be falsy?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When there are no changes to save, we don't need to block navigation away? This is what this is doing.

<form onSubmit={form.handleSubmit(handleSubmit, handleError)}>
<div className="max-w-screen-xl mx-auto flex w-full gap-x-8">
<div className="flex-1 w-0">
Expand Down
44 changes: 44 additions & 0 deletions components/link.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
'use client';
import { startTransition } from 'react';
import NextLink from 'next/link';
import { useRouter } from 'next/navigation';
import { useIsBlocked } from './navigation-block';

/**
* A custom Link component that wraps Next.js's next/link component.
*/
export function Link({
href,
children,
replace,
...rest
}: Parameters<typeof NextLink>[0]) {
const router = useRouter();
const isBlocked = useIsBlocked();

return (
<NextLink
href={href}
onClick={(e) => {
e.preventDefault();

// Cancel navigation
if (isBlocked && !window.confirm('Do you really want to leave?')) {
return;
}

startTransition(() => {
const url = href.toString();
if (replace) {
router.replace(url);
} else {
router.push(url);
}
});
}}
{...rest}
>
{children}
</NextLink>
);
}
Comment on lines +1 to +44
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where is this coming from and where is it used? I don't see it imported in your other commits..?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

68 changes: 68 additions & 0 deletions components/navigation-block.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
// components/navigation-block.tsx
'use client';

import {
Dispatch,
SetStateAction,
createContext,
useContext,
useEffect,
useState,
} from 'react';

const NavigationBlockerContext = createContext<
[isBlocked: boolean, setBlocked: Dispatch<SetStateAction<boolean>>]
>([false, () => { }]);

export function NavigationBlockerProvider({
children,
}: {
children: React.ReactNode;
}) {
// [isBlocked, setBlocked]
const state = useState(false);
return (
<NavigationBlockerContext.Provider value={state}>
{children}
</NavigationBlockerContext.Provider>
);
}

export function useIsBlocked() {
const [isBlocked] = useContext(NavigationBlockerContext);
return isBlocked;
}

export function Blocker() {
const [isBlocked, setBlocked] = useContext(NavigationBlockerContext);
useEffect(() => {
setBlocked(() => {
return true;
});
return () => {
setBlocked(() => {
return false;
});
};
}, [isBlocked, setBlocked]);
return null;
}

export function BlockBrowserNavigation() {
const isBlocked = useIsBlocked();
useEffect(() => {
console.log({ isBlocked });
if (isBlocked) {
const showModal = (event: BeforeUnloadEvent) => {
event.preventDefault();
};

window.addEventListener('beforeunload', showModal);
return () => {
window.removeEventListener('beforeunload', showModal);
};
}
}, [isBlocked]);

return null;
}
Comment on lines +1 to +68
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where did you get this solution from? Can you explain me what it does?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

vercel/next.js#41934 (comment) - explains it there

27 changes: 15 additions & 12 deletions components/providers.tsx
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we leave the ThemeProvider as the top parent?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,23 @@ import { ThemeProvider } from "@/components/theme-provider";
import { UserProvider } from "@/contexts/user-context";
import { TooltipProvider } from "@/components/ui/tooltip";
import { User } from "@/types/user";
import { NavigationBlockerProvider, BlockBrowserNavigation } from "./navigation-block";

export function Providers({ children, user }: { children: React.ReactNode, user: User | null }) {
return (
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
<UserProvider user={user}>
<TooltipProvider>
{children}
</TooltipProvider>
</UserProvider>
</ThemeProvider>
<ThemeProvider
attribute="class"
defaultTheme="system"
enableSystem
disableTransitionOnChange
>
<UserProvider user={user}>
<TooltipProvider>
<NavigationBlockerProvider>
{children}
</NavigationBlockerProvider>
</TooltipProvider>
</UserProvider>
</ThemeProvider>
);
}