Skip to content

Commit 0afcb7a

Browse files
committed
Feedback on save
1 parent 5652f4c commit 0afcb7a

File tree

2 files changed

+62
-13
lines changed

2 files changed

+62
-13
lines changed

src/hooks/use-temporary-state.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import { useCallback, useEffect, useRef, useState } from "react";
2+
3+
type UseTemporaryStateReturnType<ValueType> = [value: ValueType, set: (value: ValueType) => void];
4+
5+
/**
6+
* @description Hook to have state that reverts to a default value after a timeout when you update it. Useful for temporarily showing messages or disabling buttons.
7+
*
8+
* @param defaultValue Default value
9+
* @param timeout Milliseconds to revert to default value after setting a temporary value
10+
* @returns [value, setTemporaryValue]
11+
*/
12+
export const useTemporaryState = <ValueType>(
13+
defaultValue: ValueType,
14+
timeout: number,
15+
): UseTemporaryStateReturnType<ValueType> => {
16+
const [value, setValue] = useState<ValueType>(defaultValue);
17+
const timeoutRef = useRef<ReturnType<typeof setTimeout>>();
18+
19+
const setTemporaryValue = useCallback(
20+
(tempValue: ValueType, revertValue?: ValueType) => {
21+
timeoutRef.current && clearTimeout(timeoutRef.current);
22+
23+
setValue(tempValue);
24+
25+
timeoutRef.current = setTimeout(() => {
26+
setValue(revertValue !== undefined ? revertValue : defaultValue);
27+
}, timeout);
28+
},
29+
[defaultValue, timeout],
30+
);
31+
32+
useEffect(() => {
33+
if (timeoutRef.current) {
34+
clearTimeout(timeoutRef.current);
35+
}
36+
}, []);
37+
38+
return [value, setTemporaryValue];
39+
};

src/popup.tsx

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { CheckboxInputField } from "~components/forms/CheckboxInputField";
1111
import { InputField } from "~components/forms/InputField";
1212
import { TextInput } from "~components/forms/TextInputField";
1313
import { ALL_ORIGINS_WILDCARD, DEFAULT_GITPOD_ENDPOINT } from "~constants";
14+
import { useTemporaryState } from "~hooks/use-temporary-state";
1415
import {
1516
STORAGE_AUTOMATICALLY_DETECT_GITPOD,
1617
STORAGE_KEY_ADDRESS,
@@ -26,25 +27,32 @@ function IndexPopup() {
2627
const [error, setError] = useState<string>();
2728

2829
const [storedAddress] = useStorage<string>(STORAGE_KEY_ADDRESS, DEFAULT_GITPOD_ENDPOINT);
29-
3030
const [address, setAddress] = useState<string>(storedAddress);
31+
const [justSaved, setJustSaved] = useTemporaryState(false, 2000);
3132

3233
const updateAddress = useCallback(
3334
(e: FormEvent) => {
3435
e.preventDefault();
3536

36-
const parsedAddress = parseEndpoint(address);
37-
setError(undefined);
38-
39-
const origin = hostToOrigin(parsedAddress);
40-
41-
storage.setItem(STORAGE_KEY_ADDRESS, parsedAddress).catch((e) => {
42-
setError(e.message);
43-
});
44-
45-
browser.permissions.request({ origins: [origin] }).catch((e) => {
37+
try {
38+
const parsedAddress = parseEndpoint(address);
39+
const origin = hostToOrigin(parsedAddress);
40+
41+
storage
42+
.setItem(STORAGE_KEY_ADDRESS, parsedAddress)
43+
.catch((e) => {
44+
setError(e.message);
45+
})
46+
.then(() => {
47+
setJustSaved(true);
48+
});
49+
50+
browser.permissions.request({ origins: [origin] }).catch((e) => {
51+
setError(e.message);
52+
});
53+
} catch (e) {
4654
setError(e.message);
47-
});
55+
}
4856
},
4957
[address, setError],
5058
);
@@ -86,7 +94,9 @@ function IndexPopup() {
8694
>
8795
<div className="flex w-full max-w-sm items-center space-x-2">
8896
<TextInput value={address} onChange={setAddress} />
89-
<Button onClick={updateAddress}>Save</Button>
97+
<Button onClick={updateAddress} className="w-20">
98+
{justSaved ? "✅" : "Save"}
99+
</Button>
90100
</div>
91101
</InputField>
92102
<CheckboxInputField

0 commit comments

Comments
 (0)