Skip to content

Commit f025b12

Browse files
committed
impr(save custom text modal): add validation for custom text name input
1 parent 42f6a16 commit f025b12

File tree

1 file changed

+37
-61
lines changed

1 file changed

+37
-61
lines changed

frontend/src/ts/modals/save-custom-text.ts

Lines changed: 37 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import * as CustomText from "../test/custom-text";
22
import * as Notifications from "../elements/notifications";
33
import * as CustomTextState from "../states/custom-text-name";
4-
import { InputIndicator } from "../elements/input-indicator";
5-
import { debounce } from "throttle-debounce";
64
import AnimatedModal, { ShowOptions } from "../utils/animated-modal";
5+
import { validateWithIndicator } from "../elements/input-validation";
6+
import { z } from "zod";
77

8-
let indicator: InputIndicator | undefined;
8+
type IncomingData = {
9+
text: string[];
10+
};
911

1012
type State = {
1113
textToSave: string[];
@@ -15,6 +17,35 @@ const state: State = {
1517
textToSave: [],
1618
};
1719

20+
const validatedInput = validateWithIndicator(
21+
$("#saveCustomTextModal .textName")[0] as HTMLInputElement,
22+
{
23+
debounceDelay: 500,
24+
schema: z
25+
.string()
26+
.min(1)
27+
.max(32)
28+
.regex(/^[\w\s-]+$/, {
29+
message:
30+
"Name can only contain letters, numbers, spaces, underscores and hyphens",
31+
}),
32+
isValid: async (value) => {
33+
const checkbox = $("#saveCustomTextModal .isLongText").prop(
34+
"checked"
35+
) as boolean;
36+
const names = CustomText.getCustomTextNames(checkbox);
37+
return !names.includes(value) ? true : "Duplicate name";
38+
},
39+
callback: (result) => {
40+
if (result.status === "success") {
41+
$("#saveCustomTextModal button.save").prop("disabled", false);
42+
} else {
43+
$("#saveCustomTextModal button.save").prop("disabled", true);
44+
}
45+
},
46+
}
47+
);
48+
1849
export async function show(options: ShowOptions<IncomingData>): Promise<void> {
1950
state.textToSave = [];
2051
void modal.show({
@@ -28,10 +59,6 @@ export async function show(options: ShowOptions<IncomingData>): Promise<void> {
2859
});
2960
}
3061

31-
function hide(): void {
32-
void modal.hide();
33-
}
34-
3562
function save(): boolean {
3663
const name = $("#saveCustomTextModal .textName").val() as string;
3764
const checkbox = $("#saveCustomTextModal .isLongText").prop(
@@ -59,69 +86,18 @@ function save(): boolean {
5986
}
6087
}
6188

62-
function updateIndicatorAndButton(): void {
63-
const val = $("#saveCustomTextModal .textName").val() as string;
64-
const checkbox = $("#saveCustomTextModal .isLongText").prop(
65-
"checked"
66-
) as boolean;
67-
68-
if (!val) {
69-
indicator?.hide();
70-
$("#saveCustomTextModal button.save").prop("disabled", true);
71-
} else {
72-
const names = CustomText.getCustomTextNames(checkbox);
73-
if (names.includes(val)) {
74-
indicator?.show("unavailable");
75-
$("#saveCustomTextModal button.save").prop("disabled", true);
76-
} else {
77-
indicator?.show("available");
78-
$("#saveCustomTextModal button.save").prop("disabled", false);
79-
}
80-
}
81-
}
82-
83-
const updateInputAndButtonDebounced = debounce(500, updateIndicatorAndButton);
84-
8589
async function setup(modalEl: HTMLElement): Promise<void> {
86-
indicator = new InputIndicator($("#saveCustomTextModal .textName"), {
87-
available: {
88-
icon: "fa-check",
89-
level: 1,
90-
},
91-
unavailable: {
92-
icon: "fa-times",
93-
level: -1,
94-
},
95-
loading: {
96-
icon: "fa-circle-notch",
97-
spinIcon: true,
98-
level: 0,
99-
},
100-
});
10190
modalEl.addEventListener("submit", (e) => {
10291
e.preventDefault();
103-
if (save()) hide();
104-
});
105-
modalEl.querySelector(".textName")?.addEventListener("input", (e) => {
106-
const val = (e.target as HTMLInputElement).value;
107-
if (val.length > 0) {
108-
indicator?.show("loading");
109-
updateInputAndButtonDebounced();
92+
if (validatedInput.isValid() === true && save()) {
93+
void modal.hide();
11094
}
11195
});
11296
modalEl.querySelector(".isLongText")?.addEventListener("input", (e) => {
113-
const val = (e.target as HTMLInputElement).value;
114-
if (val.length > 0) {
115-
indicator?.show("loading");
116-
updateInputAndButtonDebounced();
117-
}
97+
validatedInput.triggerValidation();
11898
});
11999
}
120100

121-
type IncomingData = {
122-
text: string[];
123-
};
124-
125101
const modal = new AnimatedModal<IncomingData>({
126102
dialogId: "saveCustomTextModal",
127103
setup,

0 commit comments

Comments
 (0)