Unverified 提交 04087298 authored 作者: Will Chen's avatar Will Chen 提交者: GitHub

Refactor useSupabase hook to be idiomatic (#2017)

<!-- CURSOR_SUMMARY --> > [!NOTE] > Moves Supabase data fetching/mutations to React Query and aligns UI with new query states for clearer loading/errors and cache-driven updates. > > - Removed most Supabase atoms; kept `lastLogTimestampAtom` only > - New `useSupabase` exposes React Query queries (`organizations`, `projects`, `branches`) and mutations (delete org, set/unset app project, edge logs) with invalidate/refetch helpers > - `SupabaseConnector` and `SupabaseIntegration` now use `refetch*`, granular `isLoading*/error` flags, and updated handlers; branch select disabled via `isLoadingBranches`/`isSettingAppProject` > - `PreviewPanel` switches `loadEdgeLogs` to accept `{ projectId, organizationSlug }` and continues polling > - OAuth return flow now calls `refetchOrganizations`/`refetchProjects` instead of manual load functions > > <sup>Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit 573df5298f323854d4a8aa1ce5903b99e4caba62. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot).</sup> <!-- /CURSOR_SUMMARY --> <!-- This is an auto-generated description by cubic. --> --- ## Summary by cubic Refactored Supabase integration to use TanStack React Query for data fetching and mutations. This makes loading/error handling clearer, improves cache invalidation, and smooths the UI. - **Refactors** - Replaced Jotai state with React Query for organizations, projects, and branches; removed related atoms. - Added mutations for delete organization, set/unset app project, and edge logs; invalidates org/project queries on deletion. - Exposed granular states for organizations, projects, and branches; removed selected project state. - Updated SupabaseConnector/SupabaseIntegration to use refetch* methods and new flags; PreviewPanel now calls loadEdgeLogs with params; disables branch select while loading or setting. <sup>Written for commit 573df5298f323854d4a8aa1ce5903b99e4caba62. Summary will update automatically on new commits.</sup> <!-- End of auto-generated description by cubic. --> --------- Co-authored-by: 's avatarcubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
上级 de798def
import { atom } from "jotai"; import { atom } from "jotai";
import {
SupabaseBranch,
SupabaseOrganizationInfo,
SupabaseProject,
} from "@/ipc/ipc_types";
// Define atom for storing the list of connected Supabase organizations
export const supabaseOrganizationsAtom = atom<SupabaseOrganizationInfo[]>([]);
// Define atom for storing the list of Supabase projects
export const supabaseProjectsAtom = atom<SupabaseProject[]>([]);
export const supabaseBranchesAtom = atom<SupabaseBranch[]>([]);
// Define atom for tracking loading state
export const supabaseLoadingAtom = atom<boolean>(false);
// Define atom for storing any error that occurs during loading
export const supabaseErrorAtom = atom<Error | null>(null);
// Define atom for storing the currently selected Supabase project
export const selectedSupabaseProjectAtom = atom<string | null>(null);
// Define atom for tracking the last log timestamp per project (for incremental log loading) // Define atom for tracking the last log timestamp per project (for incremental log loading)
export const lastLogTimestampAtom = atom<Record<string, number>>({}); export const lastLogTimestampAtom = atom<Record<string, number>>({});
...@@ -47,46 +47,44 @@ export function SupabaseConnector({ appId }: { appId: number }) { ...@@ -47,46 +47,44 @@ export function SupabaseConnector({ appId }: { appId: number }) {
const { app, refreshApp } = useLoadApp(appId); const { app, refreshApp } = useLoadApp(appId);
const { lastDeepLink, clearLastDeepLink } = useDeepLink(); const { lastDeepLink, clearLastDeepLink } = useDeepLink();
const { isDarkMode } = useTheme(); const { isDarkMode } = useTheme();
// Check if there are any connected organizations
const isConnected = isSupabaseConnected(settings);
const branchesProjectId =
app?.supabaseParentProjectId || app?.supabaseProjectId;
const {
organizations,
projects,
branches,
isLoadingProjects,
isFetchingProjects,
projectsError,
isLoadingBranches,
isSettingAppProject,
refetchOrganizations,
refetchProjects,
deleteOrganization,
setAppProject,
unsetAppProject,
} = useSupabase({
branchesProjectId,
branchesOrganizationSlug: app?.supabaseOrganizationSlug,
});
useEffect(() => { useEffect(() => {
const handleDeepLink = async () => { const handleDeepLink = async () => {
if (lastDeepLink?.type === "supabase-oauth-return") { if (lastDeepLink?.type === "supabase-oauth-return") {
await refreshSettings(); await refreshSettings();
await loadOrganizations(); await refetchOrganizations();
await loadProjects(); await refetchProjects();
await refreshApp(); await refreshApp();
clearLastDeepLink(); clearLastDeepLink();
} }
}; };
handleDeepLink(); handleDeepLink();
}, [lastDeepLink?.timestamp]); }, [lastDeepLink?.timestamp]);
const {
organizations,
projects,
loading,
error,
loadOrganizations,
deleteOrganization,
loadProjects,
branches,
loadBranches,
setAppProject,
unsetAppProject,
} = useSupabase();
// Check if there are any connected organizations
const isConnected = isSupabaseConnected(settings);
useEffect(() => {
// Load organizations and projects when the component mounts
loadOrganizations();
}, [loadOrganizations]);
useEffect(() => {
// Load projects when organizations are available
if (isConnected) {
loadProjects();
}
}, [isConnected, loadProjects]);
const handleProjectSelect = async (projectValue: string) => { const handleProjectSelect = async (projectValue: string) => {
try { try {
...@@ -145,17 +143,6 @@ export function SupabaseConnector({ appId }: { appId: number }) { ...@@ -145,17 +143,6 @@ export function SupabaseConnector({ appId }: { appId: number }) {
} }
}; };
const projectIdForBranches =
app?.supabaseParentProjectId || app?.supabaseProjectId;
useEffect(() => {
if (projectIdForBranches) {
loadBranches(
projectIdForBranches,
app?.supabaseOrganizationSlug ?? undefined,
);
}
}, [projectIdForBranches, loadBranches, app?.supabaseOrganizationSlug]);
const handleUnsetProject = async () => { const handleUnsetProject = async () => {
try { try {
await unsetAppProject(appId); await unsetAppProject(appId);
...@@ -171,7 +158,6 @@ export function SupabaseConnector({ appId }: { appId: number }) { ...@@ -171,7 +158,6 @@ export function SupabaseConnector({ appId }: { appId: number }) {
try { try {
await deleteOrganization({ organizationSlug }); await deleteOrganization({ organizationSlug });
toast.success("Organization disconnected successfully"); toast.success("Organization disconnected successfully");
await loadProjects();
} catch (error) { } catch (error) {
toast.error("Failed to disconnect organization: " + error); toast.error("Failed to disconnect organization: " + error);
} }
...@@ -242,7 +228,7 @@ export function SupabaseConnector({ appId }: { appId: number }) { ...@@ -242,7 +228,7 @@ export function SupabaseConnector({ appId }: { appId: number }) {
toast.error("Failed to set branch: " + error); toast.error("Failed to set branch: " + error);
} }
}} }}
disabled={loading} disabled={isLoadingBranches || isSettingAppProject}
> >
<SelectTrigger <SelectTrigger
id="supabase-branch-select" id="supabase-branch-select"
...@@ -301,18 +287,18 @@ export function SupabaseConnector({ appId }: { appId: number }) { ...@@ -301,18 +287,18 @@ export function SupabaseConnector({ appId }: { appId: number }) {
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent> <CardContent>
{loading ? ( {isLoadingProjects || isFetchingProjects ? (
<div className="space-y-2"> <div className="space-y-2">
<Skeleton className="h-4 w-full" /> <Skeleton className="h-4 w-full" />
<Skeleton className="h-10 w-full" /> <Skeleton className="h-10 w-full" />
</div> </div>
) : error ? ( ) : projectsError ? (
<div className="text-red-500"> <div className="text-red-500">
Error loading projects: {error.message} Error loading projects: {projectsError.message}
<Button <Button
variant="outline" variant="outline"
className="mt-2" className="mt-2"
onClick={() => loadProjects()} onClick={() => refetchProjects()}
> >
Retry Retry
</Button> </Button>
......
import { useState, useEffect } from "react"; import { useState } from "react";
import { Button } from "@/components/ui/button"; import { Button } from "@/components/ui/button";
import { Switch } from "@/components/ui/switch"; import { Switch } from "@/components/ui/switch";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
...@@ -12,13 +12,13 @@ import { isSupabaseConnected } from "@/lib/schemas"; ...@@ -12,13 +12,13 @@ import { isSupabaseConnected } from "@/lib/schemas";
export function SupabaseIntegration() { export function SupabaseIntegration() {
const { settings, updateSettings } = useSettings(); const { settings, updateSettings } = useSettings();
const { organizations, loadOrganizations, deleteOrganization } =
useSupabase();
const [isDisconnecting, setIsDisconnecting] = useState(false); const [isDisconnecting, setIsDisconnecting] = useState(false);
useEffect(() => { // Check if there are any connected organizations
loadOrganizations(); const isConnected = isSupabaseConnected(settings);
}, [loadOrganizations]);
const { organizations, refetchOrganizations, deleteOrganization } =
useSupabase();
const handleDisconnectAllFromSupabase = async () => { const handleDisconnectAllFromSupabase = async () => {
setIsDisconnecting(true); setIsDisconnecting(true);
...@@ -31,7 +31,7 @@ export function SupabaseIntegration() { ...@@ -31,7 +31,7 @@ export function SupabaseIntegration() {
}); });
if (result) { if (result) {
showSuccess("Successfully disconnected all Supabase organizations"); showSuccess("Successfully disconnected all Supabase organizations");
await loadOrganizations(); await refetchOrganizations();
} else { } else {
showError("Failed to disconnect from Supabase"); showError("Failed to disconnect from Supabase");
} }
...@@ -64,9 +64,6 @@ export function SupabaseIntegration() { ...@@ -64,9 +64,6 @@ export function SupabaseIntegration() {
} }
}; };
// Check if there are any connected organizations
const isConnected = isSupabaseConnected(settings);
if (!isConnected) { if (!isConnected) {
return null; return null;
} }
......
...@@ -117,13 +117,13 @@ export function PreviewPanel() { ...@@ -117,13 +117,13 @@ export function PreviewPanel() {
if (!projectId) return; if (!projectId) return;
// Load logs immediately // Load logs immediately
loadEdgeLogs(projectId, organizationSlug).catch((error) => { loadEdgeLogs({ projectId, organizationSlug }).catch((error) => {
console.error("Failed to load edge logs:", error); console.error("Failed to load edge logs:", error);
}); });
// Poll for new logs every 5 seconds // Poll for new logs every 5 seconds
const intervalId = setInterval(() => { const intervalId = setInterval(() => {
loadEdgeLogs(projectId, organizationSlug).catch((error) => { loadEdgeLogs({ projectId, organizationSlug }).catch((error) => {
console.error("Failed to load edge logs:", error); console.error("Failed to load edge logs:", error);
}); });
}, 5000); }, 5000);
......
差异被折叠。
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论