make checkout version and revert version fit pattern (#118)
This commit is contained in:
@@ -117,6 +117,7 @@ export function ChatPanel({
|
|||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-full">
|
<div className="flex flex-col h-full">
|
||||||
<ChatHeader
|
<ChatHeader
|
||||||
|
isVersionPaneOpen={isVersionPaneOpen}
|
||||||
isPreviewOpen={isPreviewOpen}
|
isPreviewOpen={isPreviewOpen}
|
||||||
onTogglePreview={onTogglePreview}
|
onTogglePreview={onTogglePreview}
|
||||||
onVersionClick={() => setIsVersionPaneOpen(!isVersionPaneOpen)}
|
onVersionClick={() => setIsVersionPaneOpen(!isVersionPaneOpen)}
|
||||||
|
|||||||
@@ -21,17 +21,20 @@ import { useRouter } from "@tanstack/react-router";
|
|||||||
import { selectedChatIdAtom } from "@/atoms/chatAtoms";
|
import { selectedChatIdAtom } from "@/atoms/chatAtoms";
|
||||||
import { useChats } from "@/hooks/useChats";
|
import { useChats } from "@/hooks/useChats";
|
||||||
import { showError } from "@/lib/toast";
|
import { showError } from "@/lib/toast";
|
||||||
import { useEffect, useState } from "react";
|
import { useEffect } from "react";
|
||||||
import { useStreamChat } from "@/hooks/useStreamChat";
|
import { useStreamChat } from "@/hooks/useStreamChat";
|
||||||
import { useCurrentBranch } from "@/hooks/useCurrentBranch";
|
import { useCurrentBranch } from "@/hooks/useCurrentBranch";
|
||||||
|
import { useCheckoutVersion } from "@/hooks/useCheckoutVersion";
|
||||||
|
|
||||||
interface ChatHeaderProps {
|
interface ChatHeaderProps {
|
||||||
|
isVersionPaneOpen: boolean;
|
||||||
isPreviewOpen: boolean;
|
isPreviewOpen: boolean;
|
||||||
onTogglePreview: () => void;
|
onTogglePreview: () => void;
|
||||||
onVersionClick: () => void;
|
onVersionClick: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ChatHeader({
|
export function ChatHeader({
|
||||||
|
isVersionPaneOpen,
|
||||||
isPreviewOpen,
|
isPreviewOpen,
|
||||||
onTogglePreview,
|
onTogglePreview,
|
||||||
onVersionClick,
|
onVersionClick,
|
||||||
@@ -41,7 +44,6 @@ export function ChatHeader({
|
|||||||
const { navigate } = useRouter();
|
const { navigate } = useRouter();
|
||||||
const [selectedChatId, setSelectedChatId] = useAtom(selectedChatIdAtom);
|
const [selectedChatId, setSelectedChatId] = useAtom(selectedChatIdAtom);
|
||||||
const { refreshChats } = useChats(appId);
|
const { refreshChats } = useChats(appId);
|
||||||
const [checkingOutMain, setCheckingOutMain] = useState(false);
|
|
||||||
const { isStreaming } = useStreamChat();
|
const { isStreaming } = useStreamChat();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
@@ -50,6 +52,8 @@ export function ChatHeader({
|
|||||||
refetchBranchInfo,
|
refetchBranchInfo,
|
||||||
} = useCurrentBranch(appId);
|
} = useCurrentBranch(appId);
|
||||||
|
|
||||||
|
const { checkoutVersion, isCheckingOutVersion } = useCheckoutVersion();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (appId) {
|
if (appId) {
|
||||||
refetchBranchInfo();
|
refetchBranchInfo();
|
||||||
@@ -58,19 +62,7 @@ export function ChatHeader({
|
|||||||
|
|
||||||
const handleCheckoutMainBranch = async () => {
|
const handleCheckoutMainBranch = async () => {
|
||||||
if (!appId) return;
|
if (!appId) return;
|
||||||
|
await checkoutVersion({ appId, versionId: "main" });
|
||||||
try {
|
|
||||||
setCheckingOutMain(true);
|
|
||||||
await IpcClient.getInstance().checkoutVersion({
|
|
||||||
appId,
|
|
||||||
versionId: "main",
|
|
||||||
});
|
|
||||||
await refetchBranchInfo();
|
|
||||||
} catch (error) {
|
|
||||||
showError(`Failed to checkout main branch: ${error}`);
|
|
||||||
} finally {
|
|
||||||
setCheckingOutMain(false);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleNewChat = async () => {
|
const handleNewChat = async () => {
|
||||||
@@ -100,7 +92,8 @@ export function ChatHeader({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col w-full @container">
|
<div className="flex flex-col w-full @container">
|
||||||
{isNotMainBranch && (
|
{/* If the version pane is open, it's expected to not always be on the main branch. */}
|
||||||
|
{isNotMainBranch && !isVersionPaneOpen && (
|
||||||
<div className="flex flex-col @sm:flex-row items-center justify-between px-4 py-2 bg-amber-100 dark:bg-amber-900 text-amber-800 dark:text-amber-200">
|
<div className="flex flex-col @sm:flex-row items-center justify-between px-4 py-2 bg-amber-100 dark:bg-amber-900 text-amber-800 dark:text-amber-200">
|
||||||
<div className="flex items-center gap-2 text-sm">
|
<div className="flex items-center gap-2 text-sm">
|
||||||
<GitBranch size={16} />
|
<GitBranch size={16} />
|
||||||
@@ -138,9 +131,9 @@ export function ChatHeader({
|
|||||||
variant="outline"
|
variant="outline"
|
||||||
size="sm"
|
size="sm"
|
||||||
onClick={handleCheckoutMainBranch}
|
onClick={handleCheckoutMainBranch}
|
||||||
disabled={checkingOutMain || branchInfoLoading}
|
disabled={isCheckingOutVersion || branchInfoLoading}
|
||||||
>
|
>
|
||||||
{checkingOutMain ? "Checking out..." : "Switch to main branch"}
|
{isCheckingOutVersion ? "Checking out..." : "Switch to main branch"}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import { useVersions } from "@/hooks/useVersions";
|
|||||||
import { formatDistanceToNow } from "date-fns";
|
import { formatDistanceToNow } from "date-fns";
|
||||||
import { RotateCcw, X } from "lucide-react";
|
import { RotateCcw, X } from "lucide-react";
|
||||||
import type { Version } from "@/ipc/ipc_types";
|
import type { Version } from "@/ipc/ipc_types";
|
||||||
import { IpcClient } from "@/ipc/ipc_client";
|
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { useEffect } from "react";
|
import { useEffect, useRef, useState } from "react";
|
||||||
|
import { useCheckoutVersion } from "@/hooks/useCheckoutVersion";
|
||||||
|
|
||||||
interface VersionPaneProps {
|
interface VersionPaneProps {
|
||||||
isVisible: boolean;
|
isVisible: boolean;
|
||||||
@@ -15,31 +15,69 @@ interface VersionPaneProps {
|
|||||||
|
|
||||||
export function VersionPane({ isVisible, onClose }: VersionPaneProps) {
|
export function VersionPane({ isVisible, onClose }: VersionPaneProps) {
|
||||||
const appId = useAtomValue(selectedAppIdAtom);
|
const appId = useAtomValue(selectedAppIdAtom);
|
||||||
const { versions, loading, refreshVersions, revertVersion } =
|
const {
|
||||||
useVersions(appId);
|
versions: liveVersions,
|
||||||
|
refreshVersions,
|
||||||
|
revertVersion,
|
||||||
|
} = useVersions(appId);
|
||||||
const [selectedVersionId, setSelectedVersionId] = useAtom(
|
const [selectedVersionId, setSelectedVersionId] = useAtom(
|
||||||
selectedVersionIdAtom,
|
selectedVersionIdAtom,
|
||||||
);
|
);
|
||||||
|
const { checkoutVersion, isCheckingOutVersion } = useCheckoutVersion();
|
||||||
|
const wasVisibleRef = useRef(false);
|
||||||
|
const [cachedVersions, setCachedVersions] = useState<Version[]>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
async function updateVersions() {
|
async function updatePaneState() {
|
||||||
// Refresh versions in case the user updated versions outside of the app
|
// When pane becomes visible after being closed
|
||||||
// (e.g. manually using git).
|
if (isVisible && !wasVisibleRef.current) {
|
||||||
// Avoid loading state which causes brief flash of loading state.
|
if (appId) {
|
||||||
|
await refreshVersions();
|
||||||
|
setCachedVersions(liveVersions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset when closing
|
||||||
if (!isVisible && selectedVersionId) {
|
if (!isVisible && selectedVersionId) {
|
||||||
setSelectedVersionId(null);
|
setSelectedVersionId(null);
|
||||||
await IpcClient.getInstance().checkoutVersion({
|
if (appId) {
|
||||||
appId: appId!,
|
await checkoutVersion({ appId, versionId: "main" });
|
||||||
versionId: "main",
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
refreshVersions();
|
|
||||||
}
|
}
|
||||||
updateVersions();
|
|
||||||
}, [isVisible, refreshVersions]);
|
wasVisibleRef.current = isVisible;
|
||||||
|
}
|
||||||
|
updatePaneState();
|
||||||
|
}, [
|
||||||
|
isVisible,
|
||||||
|
selectedVersionId,
|
||||||
|
setSelectedVersionId,
|
||||||
|
appId,
|
||||||
|
checkoutVersion,
|
||||||
|
refreshVersions,
|
||||||
|
liveVersions,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Initial load of cached versions when live versions become available
|
||||||
|
useEffect(() => {
|
||||||
|
if (isVisible && liveVersions.length > 0 && cachedVersions.length === 0) {
|
||||||
|
setCachedVersions(liveVersions);
|
||||||
|
}
|
||||||
|
}, [isVisible, liveVersions, cachedVersions.length]);
|
||||||
|
|
||||||
if (!isVisible) {
|
if (!isVisible) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleVersionClick = async (versionOid: string) => {
|
||||||
|
if (appId) {
|
||||||
|
setSelectedVersionId(versionOid);
|
||||||
|
await checkoutVersion({ appId, versionId: versionOid });
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const versions = cachedVersions.length > 0 ? cachedVersions : liveVersions;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full border-t border-2 border-border w-full">
|
<div className="h-full border-t border-2 border-border w-full">
|
||||||
<div className="p-2 border-b border-border flex items-center justify-between">
|
<div className="p-2 border-b border-border flex items-center justify-between">
|
||||||
@@ -53,26 +91,25 @@ export function VersionPane({ isVisible, onClose }: VersionPaneProps) {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<div className="overflow-y-auto h-[calc(100%-60px)]">
|
<div className="overflow-y-auto h-[calc(100%-60px)]">
|
||||||
{loading ? (
|
{versions.length === 0 ? (
|
||||||
<div className="p-4 ">Loading versions...</div>
|
|
||||||
) : versions.length === 0 ? (
|
|
||||||
<div className="p-4 ">No versions available</div>
|
<div className="p-4 ">No versions available</div>
|
||||||
) : (
|
) : (
|
||||||
<div className="divide-y divide-border">
|
<div className="divide-y divide-border">
|
||||||
{versions.map((version: Version, index) => (
|
{versions.map((version: Version, index) => (
|
||||||
<div
|
<div
|
||||||
key={version.oid}
|
key={version.oid}
|
||||||
className={`px-4 py-2 hover:bg-(--background-lightest) cursor-pointer ${
|
className={cn(
|
||||||
selectedVersionId === version.oid
|
"px-4 py-2 hover:bg-(--background-lightest) cursor-pointer",
|
||||||
? "bg-(--background-lightest)"
|
selectedVersionId === version.oid &&
|
||||||
: ""
|
"bg-(--background-lightest)",
|
||||||
}`}
|
isCheckingOutVersion &&
|
||||||
|
selectedVersionId === version.oid &&
|
||||||
|
"opacity-50 cursor-not-allowed",
|
||||||
|
)}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
IpcClient.getInstance().checkoutVersion({
|
if (!isCheckingOutVersion) {
|
||||||
appId: appId!,
|
handleVersionClick(version.oid);
|
||||||
versionId: version.oid,
|
}
|
||||||
});
|
|
||||||
setSelectedVersionId(version.oid);
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
@@ -115,6 +152,8 @@ export function VersionPane({ isVisible, onClose }: VersionPaneProps) {
|
|||||||
await revertVersion({
|
await revertVersion({
|
||||||
versionId: version.oid,
|
versionId: version.oid,
|
||||||
});
|
});
|
||||||
|
// Close the pane after revert to force a refresh on next open
|
||||||
|
onClose();
|
||||||
}}
|
}}
|
||||||
className={cn(
|
className={cn(
|
||||||
"invisible mt-1 flex items-center gap-1 px-2 py-0.5 text-sm font-medium bg-(--primary) text-(--primary-foreground) hover:bg-background-lightest rounded-md transition-colors",
|
"invisible mt-1 flex items-center gap-1 px-2 py-0.5 text-sm font-medium bg-(--primary) text-(--primary-foreground) hover:bg-background-lightest rounded-md transition-colors",
|
||||||
|
|||||||
38
src/hooks/useCheckoutVersion.ts
Normal file
38
src/hooks/useCheckoutVersion.ts
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
import { useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
|
import { IpcClient } from "@/ipc/ipc_client";
|
||||||
|
|
||||||
|
interface CheckoutVersionVariables {
|
||||||
|
appId: number;
|
||||||
|
versionId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useCheckoutVersion() {
|
||||||
|
const queryClient = useQueryClient();
|
||||||
|
|
||||||
|
const { isPending: isCheckingOutVersion, mutateAsync: checkoutVersion } =
|
||||||
|
useMutation<void, Error, CheckoutVersionVariables>({
|
||||||
|
mutationFn: async ({ appId, versionId }) => {
|
||||||
|
if (appId === null) {
|
||||||
|
// Should be caught by UI logic before calling, but as a safeguard.
|
||||||
|
throw new Error("App ID is null, cannot checkout version.");
|
||||||
|
}
|
||||||
|
const ipcClient = IpcClient.getInstance();
|
||||||
|
await ipcClient.checkoutVersion({ appId, versionId });
|
||||||
|
},
|
||||||
|
onSuccess: (_, variables) => {
|
||||||
|
// Invalidate queries that depend on the current version/branch
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: ["currentBranch", variables.appId],
|
||||||
|
});
|
||||||
|
queryClient.invalidateQueries({
|
||||||
|
queryKey: ["versions", variables.appId],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
meta: { showErrorToast: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
checkoutVersion,
|
||||||
|
isCheckingOutVersion,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import { useCallback, useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useAtom, useAtomValue } from "jotai";
|
import { useAtom, useAtomValue } from "jotai";
|
||||||
import { versionsListAtom } from "@/atoms/appAtoms";
|
import { versionsListAtom } from "@/atoms/appAtoms";
|
||||||
import { IpcClient } from "@/ipc/ipc_client";
|
import { IpcClient } from "@/ipc/ipc_client";
|
||||||
import { showError } from "@/lib/toast";
|
|
||||||
import { chatMessagesAtom, selectedChatIdAtom } from "@/atoms/chatAtoms";
|
import { chatMessagesAtom, selectedChatIdAtom } from "@/atoms/chatAtoms";
|
||||||
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
|
||||||
import type { Version } from "@/ipc/ipc_types";
|
import type { Version } from "@/ipc/ipc_types";
|
||||||
@@ -41,33 +41,28 @@ export function useVersions(appId: number | null) {
|
|||||||
const revertVersionMutation = useMutation<void, Error, { versionId: string }>(
|
const revertVersionMutation = useMutation<void, Error, { versionId: string }>(
|
||||||
{
|
{
|
||||||
mutationFn: async ({ versionId }: { versionId: string }) => {
|
mutationFn: async ({ versionId }: { versionId: string }) => {
|
||||||
if (appId === null) {
|
const currentAppId = appId;
|
||||||
|
if (currentAppId === null) {
|
||||||
throw new Error("App ID is null");
|
throw new Error("App ID is null");
|
||||||
}
|
}
|
||||||
const ipcClient = IpcClient.getInstance();
|
const ipcClient = IpcClient.getInstance();
|
||||||
await ipcClient.revertVersion({ appId, previousVersionId: versionId });
|
await ipcClient.revertVersion({
|
||||||
|
appId: currentAppId,
|
||||||
|
previousVersionId: versionId,
|
||||||
|
});
|
||||||
},
|
},
|
||||||
onSuccess: async () => {
|
onSuccess: async () => {
|
||||||
await queryClient.invalidateQueries({ queryKey: ["versions", appId] });
|
await queryClient.invalidateQueries({ queryKey: ["versions", appId] });
|
||||||
|
await queryClient.invalidateQueries({
|
||||||
|
queryKey: ["currentBranch", appId],
|
||||||
|
});
|
||||||
if (selectedChatId) {
|
if (selectedChatId) {
|
||||||
const chat = await IpcClient.getInstance().getChat(selectedChatId);
|
const chat = await IpcClient.getInstance().getChat(selectedChatId);
|
||||||
setMessages(chat.messages);
|
setMessages(chat.messages);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onError: (e: Error) => {
|
meta: { showErrorToast: true },
|
||||||
showError(e);
|
|
||||||
},
|
},
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const revertVersion = useCallback(
|
|
||||||
async ({ versionId }: { versionId: string }) => {
|
|
||||||
if (appId === null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await revertVersionMutation.mutateAsync({ versionId });
|
|
||||||
},
|
|
||||||
[appId, revertVersionMutation],
|
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -75,6 +70,6 @@ export function useVersions(appId: number | null) {
|
|||||||
loading,
|
loading,
|
||||||
error,
|
error,
|
||||||
refreshVersions,
|
refreshVersions,
|
||||||
revertVersion,
|
revertVersion: revertVersionMutation.mutateAsync,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ export function registerVersionHandlers() {
|
|||||||
appId,
|
appId,
|
||||||
previousVersionId,
|
previousVersionId,
|
||||||
}: { appId: number; previousVersionId: string },
|
}: { appId: number; previousVersionId: string },
|
||||||
) => {
|
): Promise<void> => {
|
||||||
return withLock(appId, async () => {
|
return withLock(appId, async () => {
|
||||||
const app = await db.query.apps.findFirst({
|
const app = await db.query.apps.findFirst({
|
||||||
where: eq(apps.id, appId),
|
where: eq(apps.id, appId),
|
||||||
@@ -205,8 +205,6 @@ export function registerVersionHandlers() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { success: true };
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
logger.error(
|
logger.error(
|
||||||
`Error reverting to version ${previousVersionId} for app ${appId}:`,
|
`Error reverting to version ${previousVersionId} for app ${appId}:`,
|
||||||
@@ -220,7 +218,10 @@ export function registerVersionHandlers() {
|
|||||||
|
|
||||||
ipcMain.handle(
|
ipcMain.handle(
|
||||||
"checkout-version",
|
"checkout-version",
|
||||||
async (_, { appId, versionId }: { appId: number; versionId: string }) => {
|
async (
|
||||||
|
_,
|
||||||
|
{ appId, versionId }: { appId: number; versionId: string },
|
||||||
|
): Promise<void> => {
|
||||||
return withLock(appId, async () => {
|
return withLock(appId, async () => {
|
||||||
const app = await db.query.apps.findFirst({
|
const app = await db.query.apps.findFirst({
|
||||||
where: eq(apps.id, appId),
|
where: eq(apps.id, appId),
|
||||||
@@ -240,8 +241,6 @@ export function registerVersionHandlers() {
|
|||||||
ref: versionId,
|
ref: versionId,
|
||||||
force: true,
|
force: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
return { success: true };
|
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
logger.error(
|
logger.error(
|
||||||
`Error checking out version ${versionId} for app ${appId}:`,
|
`Error checking out version ${versionId} for app ${appId}:`,
|
||||||
|
|||||||
@@ -458,17 +458,11 @@ export class IpcClient {
|
|||||||
}: {
|
}: {
|
||||||
appId: number;
|
appId: number;
|
||||||
previousVersionId: string;
|
previousVersionId: string;
|
||||||
}): Promise<{ success: boolean }> {
|
}): Promise<void> {
|
||||||
try {
|
await this.ipcRenderer.invoke("revert-version", {
|
||||||
const result = await this.ipcRenderer.invoke("revert-version", {
|
|
||||||
appId,
|
appId,
|
||||||
previousVersionId,
|
previousVersionId,
|
||||||
});
|
});
|
||||||
return result;
|
|
||||||
} catch (error) {
|
|
||||||
showError(error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checkout a specific version without creating a revert commit
|
// Checkout a specific version without creating a revert commit
|
||||||
@@ -478,17 +472,11 @@ export class IpcClient {
|
|||||||
}: {
|
}: {
|
||||||
appId: number;
|
appId: number;
|
||||||
versionId: string;
|
versionId: string;
|
||||||
}): Promise<{ success: boolean }> {
|
}): Promise<void> {
|
||||||
try {
|
await this.ipcRenderer.invoke("checkout-version", {
|
||||||
const result = await this.ipcRenderer.invoke("checkout-version", {
|
|
||||||
appId,
|
appId,
|
||||||
versionId,
|
versionId,
|
||||||
});
|
});
|
||||||
return result;
|
|
||||||
} catch (error) {
|
|
||||||
showError(error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the current branch of an app
|
// Get the current branch of an app
|
||||||
|
|||||||
8
src/lib/assert.ts
Normal file
8
src/lib/assert.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
export function assertExists<T>(
|
||||||
|
value: T,
|
||||||
|
message: string,
|
||||||
|
): asserts value is NonNullable<T> {
|
||||||
|
if (value === undefined || value === null) {
|
||||||
|
throw new Error(message);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -9,6 +9,7 @@ import {
|
|||||||
QueryCache,
|
QueryCache,
|
||||||
QueryClient,
|
QueryClient,
|
||||||
QueryClientProvider,
|
QueryClientProvider,
|
||||||
|
MutationCache,
|
||||||
} from "@tanstack/react-query";
|
} from "@tanstack/react-query";
|
||||||
import { showError } from "./lib/toast";
|
import { showError } from "./lib/toast";
|
||||||
|
|
||||||
@@ -39,6 +40,13 @@ const queryClient = new QueryClient({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
mutationCache: new MutationCache({
|
||||||
|
onError: (error, _variables, _context, mutation) => {
|
||||||
|
if (mutation.meta?.showErrorToast) {
|
||||||
|
showError(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
const posthogClient = posthog.init(
|
const posthogClient = posthog.init(
|
||||||
|
|||||||
Reference in New Issue
Block a user