Skip to content

Commit d46da49

Browse files
committed
refactor: implement useDialogSnapshot for stable state management across various components
1 parent 5f87ee3 commit d46da49

27 files changed

Lines changed: 226 additions & 158 deletions

File tree

frontend/features/admin/components/sections/about/admin-about.tsx

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ import {
2828
writeCachedLatestRelease,
2929
} from "@/features/admin/model/update-check";
3030
import { AboutSettingsContent } from "@/shared/components/about-settings-content";
31+
import { useDialogSnapshot } from "@/shared/hooks/use-dialog-snapshot";
3132
import { cn } from "@/lib/utils";
3233

3334
type GitHubRelease = {
@@ -133,30 +134,31 @@ function UpdateResultDialog({
133134
}) {
134135
const t = useTranslations("adminUsers.aboutPage");
135136
const currentVersion = formatReleaseVersion(packageMeta.version);
136-
const latestVersion = state?.type === "available" ? formatReleaseVersion(state.release.version) : "";
137+
const stableState = useDialogSnapshot(state);
138+
const latestVersion = stableState?.type === "available" ? formatReleaseVersion(stableState.release.version) : "";
137139

138140
return (
139141
<Dialog open={state !== null} onOpenChange={onOpenChange}>
140142
<DialogContent className="flex max-h-[min(86vh,760px)] flex-col gap-0 overflow-hidden p-0 sm:max-w-[420px]">
141143
<DialogHeader className="shrink-0 px-4 py-4">
142144
<DialogTitle>
143-
{state?.type === "available"
145+
{stableState?.type === "available"
144146
? t("updateDialog.availableTitle")
145-
: state?.type === "failed"
147+
: stableState?.type === "failed"
146148
? t("updateDialog.failedTitle")
147149
: t("updateDialog.currentTitle")}
148150
</DialogTitle>
149151
<DialogDescription>
150-
{state?.type === "available"
152+
{stableState?.type === "available"
151153
? t("updateDialog.availableDescription", { current: currentVersion, latest: latestVersion })
152-
: state?.type === "failed"
154+
: stableState?.type === "failed"
153155
? t("updateDialog.failedDescription")
154156
: t("updateDialog.currentDescription", { current: currentVersion })}
155157
</DialogDescription>
156158
</DialogHeader>
157159

158160
<div className="min-h-0 flex-1 overflow-y-auto px-4 py-2">
159-
{state?.type === "available" ? (
161+
{stableState?.type === "available" ? (
160162
<div className="rounded-md bg-muted/50 px-3 py-2 text-xs">
161163
<div className="grid grid-cols-[auto_1fr] gap-x-4 gap-y-2">
162164
<span className="text-muted-foreground">{t("updateDialog.currentVersion")}</span>
@@ -174,14 +176,14 @@ function UpdateResultDialog({
174176
{t("updateDialog.close")}
175177
</Button>
176178
</DialogClose>
177-
{state?.type === "failed" ? (
179+
{stableState?.type === "failed" ? (
178180
<Button type="button" onClick={onRetry}>
179181
{t("updateDialog.retry")}
180182
</Button>
181183
) : null}
182-
{state?.type === "available" ? (
184+
{stableState?.type === "available" ? (
183185
<Button asChild type="button">
184-
<a href={state.release.url} target="_blank" rel="noopener noreferrer">
186+
<a href={stableState.release.url} target="_blank" rel="noopener noreferrer">
185187
{t("updateDialog.openRelease")}
186188
</a>
187189
</Button>

frontend/features/admin/components/sections/accounts/accounts-users.tsx

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@ import type { ImportOpenWebUIUsersData, ImportOpenWebUIUsersRequest } from "@/fe
4747
import { resolveAvatarImageSrc } from "@/shared/lib/avatar";
4848
import { useAuthSession } from "@/shared/auth/auth-session-context";
4949
import { resolveAccessToken } from "@/shared/auth/resolve-access-token";
50+
import { useDialogSnapshot } from "@/shared/hooks/use-dialog-snapshot";
5051
import { TimeZoneSelect } from "@/shared/components/time-zone-select";
5152
import type { AdminUserRole, AdminUserStatus } from "@/features/admin/api/admin.types";
5253
import type { AdminBillingMode } from "@/features/admin/api/billing.types";
@@ -514,6 +515,8 @@ export function AccountsUsers({
514515
onSetUsers,
515516
onSetTotal,
516517
});
518+
const stableEditDialogTarget = useDialogSnapshot(editDialogTarget);
519+
const stableResetPasswordDraft = useDialogSnapshot(resetDialogTarget ? resetPasswordDraft : null) ?? "";
517520
const virtualRows = useVirtualTableRows(filteredItems, {
518521
enabled: filteredItems.length > 100,
519522
estimateSize: 40,
@@ -945,16 +948,16 @@ export function AccountsUsers({
945948
onSubmit={handleImportOpenWebUI}
946949
/>
947950

948-
{editDialogTarget ? (
951+
{stableEditDialogTarget ? (
949952
<EditUserSheet
950-
open
953+
open={Boolean(editDialogTarget)}
951954
onOpenChange={(open) => {
952955
if (!open && pendingAction !== "edit") {
953956
setEditDialogTarget(null);
954957
}
955958
}}
956959
pending={pendingAction === "edit"}
957-
editDialogTarget={editDialogTarget}
960+
editDialogTarget={stableEditDialogTarget}
958961
editPayload={editPayload}
959962
setEditPayload={setEditPayload}
960963
billingMode={billingMode}
@@ -966,27 +969,27 @@ export function AccountsUsers({
966969
onOpenEditAvatarDialog={() => {
967970
setAvatarDialog({
968971
mode: "edit",
969-
target: editDialogTarget,
970-
value: editPayload.avatarURL.trim() || editDialogTarget.avatarURL.trim(),
972+
target: stableEditDialogTarget,
973+
value: editPayload.avatarURL.trim() || stableEditDialogTarget.avatarURL.trim(),
971974
});
972975
}}
973976
onOpenResetPasswordDialog={() => {
974-
setResetDialogTarget(editDialogTarget);
977+
setResetDialogTarget(stableEditDialogTarget);
975978
setResetPasswordDraft("");
976979
}}
977980
onOpenResetTwoFactorDialog={() => {
978-
setResetTwoFactorDialogTarget(editDialogTarget);
981+
setResetTwoFactorDialogTarget(stableEditDialogTarget);
979982
}}
980983
onOpenRevokeDialog={() => {
981-
setRevokeDialogTarget(editDialogTarget);
984+
setRevokeDialogTarget(stableEditDialogTarget);
982985
}}
983986
onOpenDeleteDialog={() => {
984-
setDeleteDialogTarget(editDialogTarget);
987+
setDeleteDialogTarget(stableEditDialogTarget);
985988
}}
986-
resetPasswordPending={pendingAction === "reset-password" && actionUserID === editDialogTarget.id}
987-
resetTwoFactorPending={pendingAction === "reset-2fa" && actionUserID === editDialogTarget.id}
988-
revokePending={pendingAction === "revoke-sessions" && actionUserID === editDialogTarget.id}
989-
deletePending={pendingAction === "delete" && actionUserID === editDialogTarget.id}
989+
resetPasswordPending={pendingAction === "reset-password" && actionUserID === stableEditDialogTarget.id}
990+
resetTwoFactorPending={pendingAction === "reset-2fa" && actionUserID === stableEditDialogTarget.id}
991+
revokePending={pendingAction === "revoke-sessions" && actionUserID === stableEditDialogTarget.id}
992+
deletePending={pendingAction === "delete" && actionUserID === stableEditDialogTarget.id}
990993
resolveUserInitial={resolveUserInitial}
991994
/>
992995
) : null}
@@ -1000,7 +1003,7 @@ export function AccountsUsers({
10001003
}
10011004
}}
10021005
pending={pendingAction === "reset-password"}
1003-
password={resetPasswordDraft}
1006+
password={stableResetPasswordDraft}
10041007
onPasswordChange={setResetPasswordDraft}
10051008
onConfirm={() => void onResetPassword()}
10061009
onCancel={() => {

frontend/features/admin/components/sections/billing/billing-plan.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import type { PermissionGroup } from "@/features/admin/api/permission-groups";
1010
import { resolveAdminErrorMessage } from "@/features/admin/utils/admin-error";
1111
import { createPlanFormState, parseIntValue, parsePrice, type PlanFormState } from "@/features/admin/model/billing-settings";
1212
import { resolveAccessToken } from "@/shared/auth/resolve-access-token";
13+
import { useDialogSnapshot } from "@/shared/hooks/use-dialog-snapshot";
1314
import { PlanBillingDialog } from "@/features/admin/components/sections/billing/billing-dialogs";
1415
import { PeriodBillingTable } from "@/features/admin/components/sections/billing/billing-tables";
1516

@@ -25,6 +26,7 @@ export function BillingPlanSection({ plans, setPlans, permissionGroups, loading
2526
const [saving, setSaving] = React.useState(false);
2627
const [editPlan, setEditPlan] = React.useState<AdminBillingPlanDTO | null>(null);
2728
const [planForm, setPlanForm] = React.useState<PlanFormState | null>(null);
29+
const stablePlanForm = useDialogSnapshot(planForm);
2830

2931
function openPlanEdit(plan: AdminBillingPlanDTO) {
3032
setEditPlan(plan);
@@ -73,7 +75,7 @@ export function BillingPlanSection({ plans, setPlans, permissionGroups, loading
7375
<PlanBillingDialog
7476
open={!!editPlan && !!planForm}
7577
saving={saving}
76-
planForm={planForm}
78+
planForm={stablePlanForm}
7779
setPlanForm={setPlanForm}
7880
permissionGroups={permissionGroups}
7981
onOpenChange={(open) => {

frontend/features/admin/components/sections/billing/billing-prices.tsx

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import {
4949
import { resolveAdminErrorMessage } from "@/features/admin/utils/admin-error";
5050
import { LobeHubIcon } from "@/shared/components/lobehub-icon";
5151
import { resolveAccessToken } from "@/shared/auth/resolve-access-token";
52+
import { useDialogSnapshot } from "@/shared/hooks/use-dialog-snapshot";
5253
import { cn } from "@/lib/utils";
5354
import { KNOWN_VENDOR_OPTIONS, resolveLobeHubIconURL, resolveModelIdentity } from "@/shared/lib/model-identity";
5455

@@ -150,6 +151,9 @@ export function BillingPricesSection({ models, pricingItems, setPricingItems, lo
150151
const [officialPricingCatalogHasError, setOfficialPricingCatalogHasError] = React.useState(false);
151152
const [officialPricingSingleDialogOpen, setOfficialPricingSingleDialogOpen] = React.useState(false);
152153
const [freeSwitchPendingModel, setFreeSwitchPendingModel] = React.useState("");
154+
const stableEditRow = useDialogSnapshot(editRow);
155+
const stableForm = useDialogSnapshot(form);
156+
const stableOfficialPricingImportSuggestion = useDialogSnapshot(officialPricingImportSuggestion);
153157

154158
const rows = React.useMemo(() => buildPricingRows(models, pricingItems), [models, pricingItems]);
155159
const vendorFilterOptions = React.useMemo(() => {
@@ -689,7 +693,7 @@ export function BillingPricesSection({ models, pricingItems, setPricingItems, lo
689693
<PricingBillingDialog
690694
open={!!editRow && !!form}
691695
saving={saving}
692-
form={form}
696+
form={stableForm}
693697
setForm={setForm}
694698
onOpenChange={(open) => {
695699
if (!open && !saving) {
@@ -729,7 +733,7 @@ export function BillingPricesSection({ models, pricingItems, setPricingItems, lo
729733
</DialogHeader>
730734

731735
<div className="flex min-h-0 flex-1 flex-col gap-3 overflow-hidden px-4 py-2">
732-
{editRow ? (
736+
{stableEditRow ? (
733737
<>
734738
<div className="shrink-0 space-y-2">
735739
<div className="grid grid-cols-[minmax(0,1fr)_auto] gap-2">
@@ -909,7 +913,7 @@ export function BillingPricesSection({ models, pricingItems, setPricingItems, lo
909913
</Button>
910914
<Button
911915
type="button"
912-
disabled={!officialPricingMultiplierValid}
916+
disabled={!officialPricingMultiplierValid || !stableOfficialPricingImportSuggestion}
913917
onClick={confirmOfficialPricingImport}
914918
>
915919
{t("modelPricing.officialPricingImport")}

0 commit comments

Comments
 (0)