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 {
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)
export const lastLogTimestampAtom = atom<Record<string, number>>({});
......@@ -47,46 +47,44 @@ export function SupabaseConnector({ appId }: { appId: number }) {
const { app, refreshApp } = useLoadApp(appId);
const { lastDeepLink, clearLastDeepLink } = useDeepLink();
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(() => {
const handleDeepLink = async () => {
if (lastDeepLink?.type === "supabase-oauth-return") {
await refreshSettings();
await loadOrganizations();
await loadProjects();
await refetchOrganizations();
await refetchProjects();
await refreshApp();
clearLastDeepLink();
}
};
handleDeepLink();
}, [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) => {
try {
......@@ -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 () => {
try {
await unsetAppProject(appId);
......@@ -171,7 +158,6 @@ export function SupabaseConnector({ appId }: { appId: number }) {
try {
await deleteOrganization({ organizationSlug });
toast.success("Organization disconnected successfully");
await loadProjects();
} catch (error) {
toast.error("Failed to disconnect organization: " + error);
}
......@@ -242,7 +228,7 @@ export function SupabaseConnector({ appId }: { appId: number }) {
toast.error("Failed to set branch: " + error);
}
}}
disabled={loading}
disabled={isLoadingBranches || isSettingAppProject}
>
<SelectTrigger
id="supabase-branch-select"
......@@ -301,18 +287,18 @@ export function SupabaseConnector({ appId }: { appId: number }) {
</CardDescription>
</CardHeader>
<CardContent>
{loading ? (
{isLoadingProjects || isFetchingProjects ? (
<div className="space-y-2">
<Skeleton className="h-4 w-full" />
<Skeleton className="h-10 w-full" />
</div>
) : error ? (
) : projectsError ? (
<div className="text-red-500">
Error loading projects: {error.message}
Error loading projects: {projectsError.message}
<Button
variant="outline"
className="mt-2"
onClick={() => loadProjects()}
onClick={() => refetchProjects()}
>
Retry
</Button>
......
import { useState, useEffect } from "react";
import { useState } from "react";
import { Button } from "@/components/ui/button";
import { Switch } from "@/components/ui/switch";
import { Label } from "@/components/ui/label";
......@@ -12,13 +12,13 @@ import { isSupabaseConnected } from "@/lib/schemas";
export function SupabaseIntegration() {
const { settings, updateSettings } = useSettings();
const { organizations, loadOrganizations, deleteOrganization } =
useSupabase();
const [isDisconnecting, setIsDisconnecting] = useState(false);
useEffect(() => {
loadOrganizations();
}, [loadOrganizations]);
// Check if there are any connected organizations
const isConnected = isSupabaseConnected(settings);
const { organizations, refetchOrganizations, deleteOrganization } =
useSupabase();
const handleDisconnectAllFromSupabase = async () => {
setIsDisconnecting(true);
......@@ -31,7 +31,7 @@ export function SupabaseIntegration() {
});
if (result) {
showSuccess("Successfully disconnected all Supabase organizations");
await loadOrganizations();
await refetchOrganizations();
} else {
showError("Failed to disconnect from Supabase");
}
......@@ -64,9 +64,6 @@ export function SupabaseIntegration() {
}
};
// Check if there are any connected organizations
const isConnected = isSupabaseConnected(settings);
if (!isConnected) {
return null;
}
......
......@@ -117,13 +117,13 @@ export function PreviewPanel() {
if (!projectId) return;
// Load logs immediately
loadEdgeLogs(projectId, organizationSlug).catch((error) => {
loadEdgeLogs({ projectId, organizationSlug }).catch((error) => {
console.error("Failed to load edge logs:", error);
});
// Poll for new logs every 5 seconds
const intervalId = setInterval(() => {
loadEdgeLogs(projectId, organizationSlug).catch((error) => {
loadEdgeLogs({ projectId, organizationSlug }).catch((error) => {
console.error("Failed to load edge logs:", error);
});
}, 5000);
......
差异被折叠。
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论