Skip to content

Commit 4e07c0c

Browse files
feat(profile): disable save button when no profile changes are made
1 parent b1961a6 commit 4e07c0c

1 file changed

Lines changed: 60 additions & 28 deletions

File tree

frontend/src/ts/modals/edit-profile.ts

Lines changed: 60 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ export function show(): void {
2424
beforeAnimation: async () => {
2525
hydrateInputs();
2626
originalState = getProfileState();
27+
updateSaveButtonState();
2728
initializeCharacterCounters();
2829
},
2930
});
@@ -33,6 +34,10 @@ function hide(): void {
3334
void modal.hide();
3435
}
3536

37+
const saveButton = qsr<HTMLButtonElement>(
38+
"#editProfileModal .edit-profile-submit",
39+
);
40+
3641
const bioInput = qsr<HTMLTextAreaElement>("#editProfileModal .bio");
3742
const keyboardInput = qsr<HTMLTextAreaElement>("#editProfileModal .keyboard");
3843
const twitterInput = qsr<HTMLInputElement>("#editProfileModal .twitter");
@@ -43,10 +48,41 @@ const showActivityOnPublicProfileInput = qsr<HTMLInputElement>(
4348
"#editProfileModal .editProfileShowActivityOnPublicProfile",
4449
);
4550

51+
bioInput.on("input", () => {
52+
updateSaveButtonState();
53+
});
54+
keyboardInput.on("input", () => {
55+
updateSaveButtonState();
56+
});
57+
twitterInput.on("input", () => {
58+
updateSaveButtonState();
59+
});
60+
githubInput.on("input", () => {
61+
updateSaveButtonState();
62+
});
63+
websiteInput.on("input", () => {
64+
updateSaveButtonState();
65+
});
66+
showActivityOnPublicProfileInput.on("change", () => {
67+
updateSaveButtonState();
68+
});
69+
4670
const indicators = [
47-
addValidation(twitterInput, TwitterProfileSchema),
48-
addValidation(githubInput, GithubProfileSchema),
49-
addValidation(websiteInput, WebsiteSchema),
71+
addValidation(
72+
twitterInput,
73+
TwitterProfileSchema,
74+
() => originalState?.twitter ?? "",
75+
),
76+
addValidation(
77+
githubInput,
78+
GithubProfileSchema,
79+
() => originalState?.github ?? "",
80+
),
81+
addValidation(
82+
websiteInput,
83+
WebsiteSchema,
84+
() => originalState?.website ?? "",
85+
),
5086
];
5187

5288
let currentSelectedBadgeId = -1;
@@ -101,6 +137,7 @@ function hydrateInputs(): void {
101137

102138
badgeIdsSelect?.qsa(".badgeSelectionItem")?.removeClass("selected");
103139
(currentTarget as HTMLElement).classList.add("selected");
140+
updateSaveButtonState();
104141
});
105142

106143
indicators.forEach((it) => it.hide());
@@ -171,40 +208,33 @@ function hasProfileChanged(
171208
);
172209
}
173210

211+
function updateSaveButtonState(): void {
212+
const currentState = getProfileState();
213+
const hasChanges = hasProfileChanged(originalState, currentState);
214+
215+
const hasValidationErrors = [
216+
{ value: currentState.twitter, schema: TwitterProfileSchema },
217+
{ value: currentState.github, schema: GithubProfileSchema },
218+
{ value: currentState.website, schema: WebsiteSchema },
219+
].some(
220+
({ value, schema }) => value !== "" && !schema.safeParse(value).success,
221+
);
222+
223+
saveButton.native.disabled = !hasChanges || hasValidationErrors;
224+
}
225+
174226
async function updateProfile(): Promise<void> {
175227
const snapshot = DB.getSnapshot();
176228
if (!snapshot) return;
177229

178230
const currentState = getProfileState();
231+
179232
if (!hasProfileChanged(originalState, currentState)) {
180-
showErrorNotification("No changes to save");
233+
updateSaveButtonState();
181234
return;
182235
}
183236

184237
const updates = buildUpdatesFromState(currentState);
185-
// check for length restrictions before sending server requests
186-
const githubLengthLimit = 39;
187-
if (
188-
updates.socialProfiles?.github !== undefined &&
189-
updates.socialProfiles?.github.length > githubLengthLimit
190-
) {
191-
showErrorNotification(
192-
`GitHub username exceeds maximum allowed length (${githubLengthLimit} characters).`,
193-
);
194-
return;
195-
}
196-
197-
const twitterLengthLimit = 20;
198-
if (
199-
updates.socialProfiles?.twitter !== undefined &&
200-
updates.socialProfiles?.twitter.length > twitterLengthLimit
201-
) {
202-
showErrorNotification(
203-
`Twitter username exceeds maximum allowed length (${twitterLengthLimit} characters).`,
204-
);
205-
return;
206-
}
207-
208238
showLoaderBar();
209239
const response = await Ape.users.updateProfile({
210240
body: {
@@ -238,6 +268,7 @@ async function updateProfile(): Promise<void> {
238268
function addValidation(
239269
element: ElementWithUtils<HTMLInputElement>,
240270
schema: Zod.Schema,
271+
getOriginalValue: () => string,
241272
): InputIndicator {
242273
const indicator = new InputIndicator(element, {
243274
valid: {
@@ -257,10 +288,11 @@ function addValidation(
257288

258289
element.on("input", (event) => {
259290
const value = (event.target as HTMLInputElement).value;
260-
if (value === undefined || value === "") {
291+
if (value === undefined || value === "" || value === getOriginalValue()) {
261292
indicator.hide();
262293
return;
263294
}
295+
264296
const validationResult = schema.safeParse(value);
265297
if (!validationResult.success) {
266298
indicator.show(

0 commit comments

Comments
 (0)