11import { useFetcher } from "@remix-run/react" ;
2- import { useCallback , useEffect , useMemo , useState } from "react" ;
2+ import { useEffect , useMemo , useState } from "react" ;
33import {
44 Dialog ,
55 DialogContent ,
@@ -14,6 +14,10 @@ import { Input } from "~/components/primitives/Input";
1414import { cn } from "~/utils/cn" ;
1515import type { FlagControlType } from "~/v3/featureFlags.server" ;
1616
17+ const UNSET_VALUE = "__unset__" ;
18+
19+ const HIDDEN_FLAGS = [ "defaultWorkerInstanceGroupId" ] ;
20+
1721type LoaderData = {
1822 org : { id : string ; title : string ; slug : string } ;
1923 orgFlags : Record < string , unknown > ;
@@ -42,18 +46,15 @@ export function FeatureFlagsDialog({
4246 const loadFetcher = useFetcher < LoaderData > ( ) ;
4347 const saveFetcher = useFetcher < ActionData > ( ) ;
4448
45- // Local state for edits - keyed by flag name, value is the override or undefined (unset)
4649 const [ overrides , setOverrides ] = useState < Record < string , unknown > > ( { } ) ;
4750 const [ initialOverrides , setInitialOverrides ] = useState < Record < string , unknown > > ( { } ) ;
4851
49- // Load flags when dialog opens
5052 useEffect ( ( ) => {
5153 if ( open && orgId ) {
5254 loadFetcher . load ( `/admin/api/orgs/${ orgId } /feature-flags` ) ;
5355 }
5456 } , [ open , orgId ] ) ;
5557
56- // Sync loaded data into local state
5758 useEffect ( ( ) => {
5859 if ( loadFetcher . data ) {
5960 const loaded = loadFetcher . data . orgFlags ?? { } ;
@@ -62,7 +63,6 @@ export function FeatureFlagsDialog({
6263 }
6364 } , [ loadFetcher . data ] ) ;
6465
65- // Close on successful save
6666 useEffect ( ( ) => {
6767 if ( saveFetcher . data ?. success ) {
6868 onOpenChange ( false ) ;
@@ -73,45 +73,40 @@ export function FeatureFlagsDialog({
7373 return JSON . stringify ( overrides ) !== JSON . stringify ( initialOverrides ) ;
7474 } , [ overrides , initialOverrides ] ) ;
7575
76- const setFlagValue = useCallback ( ( key : string , value : unknown ) => {
76+ const setFlagValue = ( key : string , value : unknown ) => {
7777 setOverrides ( ( prev ) => ( { ...prev , [ key ] : value } ) ) ;
78- } , [ ] ) ;
78+ } ;
7979
80- const unsetFlag = useCallback ( ( key : string ) => {
80+ const unsetFlag = ( key : string ) => {
8181 setOverrides ( ( prev ) => {
8282 const next = { ...prev } ;
8383 delete next [ key ] ;
8484 return next ;
8585 } ) ;
86- } , [ ] ) ;
86+ } ;
8787
88- const handleSave = useCallback ( ( ) => {
88+ const handleSave = ( ) => {
8989 if ( ! orgId ) return ;
90-
9190 const body = Object . keys ( overrides ) . length === 0 ? null : overrides ;
92-
9391 saveFetcher . submit ( JSON . stringify ( body ) , {
9492 method : "POST" ,
9593 action : `/admin/api/orgs/${ orgId } /feature-flags` ,
9694 encType : "application/json" ,
9795 } ) ;
98- } , [ orgId , overrides , saveFetcher ] ) ;
96+ } ;
9997
10098 const data = loadFetcher . data ;
10199 const isLoading = loadFetcher . state === "loading" ;
102100 const isSaving = saveFetcher . state === "submitting" ;
103101
104- // Build JSON preview
105- const jsonPreview = useMemo ( ( ) => {
106- if ( Object . keys ( overrides ) . length === 0 ) return "null" ;
107- return JSON . stringify ( overrides , null , 2 ) ;
108- } , [ overrides ] ) ;
102+ const jsonPreview =
103+ Object . keys ( overrides ) . length === 0 ? "null" : JSON . stringify ( overrides , null , 2 ) ;
109104
110- // Sort flag keys alphabetically
111- const sortedFlagKeys = useMemo ( ( ) => {
112- if ( ! data ) return [ ] ;
113- return Object . keys ( data . controlTypes ) . sort ( ) ;
114- } , [ data ] ) ;
105+ const sortedFlagKeys = data
106+ ? Object . keys ( data . controlTypes )
107+ . filter ( ( key ) => ! HIDDEN_FLAGS . includes ( key ) )
108+ . sort ( )
109+ : [ ] ;
115110
116111 return (
117112 < Dialog open = { open } onOpenChange = { onOpenChange } >
@@ -126,9 +121,7 @@ export function FeatureFlagsDialog({
126121 < div className = "py-8 text-center text-sm text-text-dimmed" > Loading flags...</ div >
127122 ) : data ? (
128123 < div className = "flex flex-col gap-1.5" >
129- { sortedFlagKeys
130- . filter ( ( key ) => key !== "defaultWorkerInstanceGroupId" )
131- . map ( ( key ) => {
124+ { sortedFlagKeys . map ( ( key ) => {
132125 const control = data . controlTypes [ key ] ;
133126 const isOverridden = key in overrides ;
134127 const globalValue = data . globalFlags [ key as keyof typeof data . globalFlags ] ;
@@ -179,7 +172,7 @@ export function FeatureFlagsDialog({
179172 value = { isOverridden ? ( overrides [ key ] as string ) : undefined }
180173 options = { control . options }
181174 onChange = { ( val ) => {
182- if ( val === "__unset__" ) {
175+ if ( val === UNSET_VALUE ) {
183176 unsetFlag ( key ) ;
184177 } else {
185178 setFlagValue ( key , val ) ;
@@ -210,7 +203,6 @@ export function FeatureFlagsDialog({
210203 ) : null }
211204 </ div >
212205
213- { /* JSON Preview */ }
214206 { data && (
215207 < details className = "mt-2" >
216208 < summary className = "cursor-pointer text-xs text-text-dimmed hover:text-text-bright" >
@@ -271,21 +263,21 @@ function EnumControl({
271263 onChange : ( val : string ) => void ;
272264 dimmed : boolean ;
273265} ) {
274- const items = [ "__unset__" , ...options ] ;
266+ const items = [ UNSET_VALUE , ...options ] ;
275267
276268 return (
277269 < Select
278270 variant = "tertiary/small"
279- value = { value ?? "__unset__" }
271+ value = { value ?? UNSET_VALUE }
280272 setValue = { onChange }
281273 items = { items }
282- text = { ( val ) => ( val === "__unset__" ? "unset" : val ) }
274+ text = { ( val ) => ( val === UNSET_VALUE ? "unset" : val ) }
283275 className = { cn ( dimmed && "opacity-50" ) }
284276 >
285277 { ( items ) =>
286278 items . map ( ( item ) => (
287279 < SelectItem key = { item } value = { item } >
288- { item === "__unset__" ? "unset" : item }
280+ { item === UNSET_VALUE ? "unset" : item }
289281 </ SelectItem >
290282 ) )
291283 }
0 commit comments