Skip to content

Commit 15ccebf

Browse files
committed
feat: replace branch text field with search select dropdown
Fetch available branches from the platform API after credential validation and display them in an Autocomplete dropdown. Users can select from existing branches or type a custom name. The dropdown only appears when branching is enabled on the project.
1 parent 44dfc72 commit 15ccebf

2 files changed

Lines changed: 124 additions & 14 deletions

File tree

src/popup/TolgeeDetector.tsx

Lines changed: 69 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1-
import React from 'react';
1+
import React, { useState } from 'react';
22
import {
3+
Autocomplete,
34
Box,
45
Button,
56
CircularProgress,
@@ -26,7 +27,9 @@ export const TolgeeDetector = () => {
2627
libConfig,
2728
tolgeePresent,
2829
credentialsCheck,
30+
branches,
2931
} = state;
32+
const [branchOpen, setBranchOpen] = useState(false);
3033

3134
const handleApplyChange = async () => {
3235
if (appliedValues) {
@@ -129,20 +132,73 @@ export const TolgeeDetector = () => {
129132
</FormControl>
130133
{typeof credentialsCheck === 'object' &&
131134
credentialsCheck?.branchingEnabled && (
132-
<TextField
133-
label="Branch"
134-
variant="outlined"
135-
value={values?.branch || ''}
136-
onChange={(e: any) =>
135+
<Autocomplete
136+
style={{ marginBottom: branchOpen ? 150 : 0 }}
137+
open={branchOpen}
138+
onOpen={() => setBranchOpen(true)}
139+
onClose={() => setBranchOpen(false)}
140+
freeSolo
141+
size="small"
142+
disablePortal
143+
slotProps={{
144+
popper: {
145+
placement: 'bottom',
146+
modifiers: [
147+
{ name: 'flip', enabled: false },
148+
],
149+
},
150+
}}
151+
ListboxProps={{ style: { maxHeight: 150 } }}
152+
options={branches ?? []}
153+
getOptionLabel={(option) =>
154+
typeof option === 'string' ? option : option.name
155+
}
156+
value={
157+
branches?.find((b) => b.name === values?.branch) ??
158+
values?.branch ??
159+
null
160+
}
161+
onChange={(_e: any, newValue: any) => {
137162
dispatch({
138163
type: 'CHANGE_VALUES',
139-
payload: { branch: e.target.value },
140-
})
141-
}
142-
onKeyDown={handleKeyDown}
143-
size="small"
144-
placeholder={libConfig?.config?.branch || 'Default branch'}
145-
helperText="Leave empty to use the branch from SDK config"
164+
payload: {
165+
branch:
166+
typeof newValue === 'string'
167+
? newValue
168+
: newValue?.name ?? '',
169+
},
170+
});
171+
}}
172+
onInputChange={(_e: any, newInput: string, reason: string) => {
173+
if (reason === 'input') {
174+
dispatch({
175+
type: 'CHANGE_VALUES',
176+
payload: { branch: newInput },
177+
});
178+
}
179+
}}
180+
renderOption={(props, option) => (
181+
<li {...props}>
182+
{option.name}
183+
{option.isDefault && (
184+
<span style={{ color: '#999', marginLeft: 6 }}>
185+
default
186+
</span>
187+
)}
188+
</li>
189+
)}
190+
renderInput={(params) => (
191+
<TextField
192+
{...params}
193+
label="Branch"
194+
variant="outlined"
195+
placeholder={
196+
libConfig?.config?.branch || 'Default branch'
197+
}
198+
helperText="Leave empty to use the branch from SDK config"
199+
onKeyDown={handleKeyDown}
200+
/>
201+
)}
146202
/>
147203
)}
148204
<Box

src/popup/useDetectorForm.tsx

Lines changed: 55 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { RuntimeMessage } from '../content/Messages';
1111

1212
type ProjectInfo = {
1313
projectName: string;
14+
projectId: number;
1415
scopes: string[];
1516
userFullName: string;
1617
branchingEnabled: boolean;
@@ -19,6 +20,11 @@ type ProjectInfo = {
1920
type CredentialsCheck = null | 'loading' | 'invalid' | ProjectInfo;
2021
type TolgeePresent = 'loading' | 'present' | 'not_present' | 'legacy';
2122

23+
type BranchOption = {
24+
name: string;
25+
isDefault: boolean;
26+
};
27+
2228
const initialState = {
2329
values: null as Values | null,
2430
storedValues: null as Values | null,
@@ -28,6 +34,7 @@ const initialState = {
2834
libConfig: null as LibConfig | null,
2935
error: null as string | null,
3036
frameId: null as number | null,
37+
branches: null as BranchOption[] | null,
3138
};
3239

3340
type State = typeof initialState;
@@ -44,7 +51,8 @@ type Action =
4451
| { type: 'APPLY_VALUES' }
4552
| { type: 'CLEAR_ALL' }
4653
| { type: 'STORE_VALUES' }
47-
| { type: 'LOAD_VALUES' };
54+
| { type: 'LOAD_VALUES' }
55+
| { type: 'SET_BRANCHES'; payload: BranchOption[] | null };
4856

4957
export const useDetectorForm = () => {
5058
const { applyRequired, apply } = useApplier();
@@ -148,6 +156,11 @@ export const useDetectorForm = () => {
148156
appliedValues: state.storedValues,
149157
values: state.storedValues,
150158
};
159+
case 'SET_BRANCHES':
160+
return {
161+
...state,
162+
branches: action.payload,
163+
};
151164
default:
152165
// @ts-expect-error action type is type uknown
153166
throw new Error(`Unknown action ${action.type}`);
@@ -271,6 +284,7 @@ export const useDetectorForm = () => {
271284
data &&
272285
setCredentialsCheck({
273286
projectName: data.projectName,
287+
projectId: data.projectId,
274288
scopes: data.scopes,
275289
userFullName: data.userFullName,
276290
branchingEnabled: data.branchingEnabled ?? false,
@@ -284,5 +298,45 @@ export const useDetectorForm = () => {
284298
};
285299
}, [checkableValues?.apiUrl, checkableValues?.apiKey]);
286300

301+
// fetch branches when credentials are valid and branching is enabled
302+
useEffect(() => {
303+
let cancelled = false;
304+
const check = state.credentialsCheck;
305+
if (
306+
typeof check === 'object' &&
307+
check?.branchingEnabled &&
308+
validateValues(checkableValues)
309+
) {
310+
const url = normalizeUrl(checkableValues!.apiUrl);
311+
fetch(
312+
`${url}/v2/projects/${check.projectId}/branches?ak=${
313+
checkableValues!.apiKey
314+
}&size=100`
315+
)
316+
.then((r) => (r.ok ? r.json() : null))
317+
.then((data) => {
318+
if (!cancelled && data?._embedded?.branches) {
319+
dispatch({
320+
type: 'SET_BRANCHES',
321+
payload: data._embedded.branches.map((b: any) => ({
322+
name: b.name,
323+
isDefault: b.isDefault,
324+
})),
325+
});
326+
}
327+
})
328+
.catch(() => {
329+
if (!cancelled) {
330+
dispatch({ type: 'SET_BRANCHES', payload: null });
331+
}
332+
});
333+
} else {
334+
dispatch({ type: 'SET_BRANCHES', payload: null });
335+
}
336+
return () => {
337+
cancelled = true;
338+
};
339+
}, [state.credentialsCheck]);
340+
287341
return [state, dispatch] as const;
288342
};

0 commit comments

Comments
 (0)