PromucFlow_constructor/app/client/src/pages/AdminSettings/Profile/index.tsx
Ankita Kinger ba4e29fb82
fix: Updating admin settings logic to fix issues on EE (#40135)
## Description

Updating admin settings logic to fix issues on EE

Fixes #`Issue Number`  
_or_  
Fixes `Issue URL`
> [!WARNING]  
> _If no issue exists, please create an issue first, and check with the
maintainers if the issue is valid._

## Automation

/ok-to-test tags="@tag.Settings"

### 🔍 Cypress test results
<!-- This is an auto-generated comment: Cypress test results  -->
> [!TIP]
> 🟢 🟢 🟢 All cypress tests have passed! 🎉 🎉 🎉
> Workflow run:
<https://github.com/appsmithorg/appsmith/actions/runs/14315577260>
> Commit: 1b0c2823290e6af5849b096d640ac856964a0d4c
> <a
href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=14315577260&attempt=1"
target="_blank">Cypress dashboard</a>.
> Tags: `@tag.Settings`
> Spec:
> <hr>Mon, 07 Apr 2025 17:54:10 UTC
<!-- end of auto-generated comment: Cypress test results  -->


## Communication
Should the DevRel and Marketing teams inform users about this change?
- [ ] Yes
- [ ] No


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

## Summary by CodeRabbit

- **New Features**
- Admin Settings now displays Profile and Organisation sections whenever
available, independent of user privileges.
- Updated category filtering ensures non-superusers see only the most
relevant options.
- Introduced a new locator for the sub-text link in Admin Settings to
improve test coverage.

- **Style**
- Enhanced link presentation in the Admin Settings page for clearer
navigation.

- **Bug Fixes**
- Simplified test logic for Admin settings, improving the reliability of
test cases.
- Ensured consistent boolean handling for user superuser status across
various components.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2025-04-08 00:41:02 +05:30

342 lines
10 KiB
TypeScript

import React, { useEffect, useMemo, useState } from "react";
import {
BottomSpace,
HeaderWrapper,
SettingsFormWrapper,
SettingsHeader,
SettingsSubHeader,
Wrapper,
} from "../components";
import { useDispatch, useSelector } from "react-redux";
import { getCurrentUser } from "selectors/usersSelectors";
import { getIsFormLoginEnabled } from "ee/selectors/organizationSelectors";
import { forgotPasswordSubmitHandler } from "pages/UserAuth/helpers";
import {
createMessage,
FORGOT_PASSWORD_SUCCESS_TEXT,
USER_DISPLAY_NAME_CHAR_CHECK_FAILED,
USER_DISPLAY_PICTURE_PLACEHOLDER,
USER_DISPLAY_NAME_PLACEHOLDER,
USER_EMAIL_PLACEHOLDER,
USER_RESET_PASSWORD,
AUTHOR_NAME,
AUTHOR_EMAIL,
RESET_BUTTON,
SAVE_BUTTON,
} from "ee/constants/messages";
import { Button, Flex, Input, Text, toast } from "@appsmith/ads";
import { logoutUser, updateUserDetails } from "actions/userActions";
import { ANONYMOUS_USERNAME } from "constants/userConstants";
import { notEmptyValidator } from "@appsmith/ads-old";
import { ALL_LANGUAGE_CHARACTERS_REGEX } from "constants/Regex";
import {
FieldWrapper,
LabelWrapper,
Loader,
ResetPasswordButton,
SettingsButtonWrapper,
SubCategory,
} from "./StyledComponents";
import UserProfileImagePicker from "./UserProfileImagePicker";
import { emailValidator } from "@appsmith/ads-old";
import {
getGlobalGitConfig,
getIsFetchingGlobalGitConfig,
} from "selectors/gitSyncSelectors";
import {
fetchGlobalGitConfigInit,
updateGlobalGitConfigInit,
} from "actions/gitSyncActions";
import { Classes } from "@blueprintjs/core";
import { useGitModEnabled } from "pages/Editor/gitSync/hooks/modHooks";
import useGlobalProfile from "git/hooks/useGlobalProfile";
const nameValidator = (
value: string,
): {
isValid: boolean;
message: string;
} => {
const notEmpty = notEmptyValidator(value);
if (!notEmpty.isValid) {
return notEmpty;
}
if (!new RegExp(`^[${ALL_LANGUAGE_CHARACTERS_REGEX} 0-9.'-]+$`).test(value)) {
return {
isValid: false,
message: createMessage(USER_DISPLAY_NAME_CHAR_CHECK_FAILED),
};
}
return {
isValid: true,
message: "",
};
};
export const Profile = () => {
const dispatch = useDispatch();
const user = useSelector(getCurrentUser);
const isFormLoginEnabled = useSelector(getIsFormLoginEnabled);
const isFetching = useSelector(getIsFetchingGlobalGitConfig);
const globalGitConfig = useSelector(getGlobalGitConfig);
const { fetchGlobalProfile, globalProfile, updateGlobalProfile } =
useGlobalProfile();
const [name, setName] = useState(user?.name || "");
const [isSaving, setIsSaving] = useState(false);
const [areFormValuesUpdated, setAreFormValuesUpdated] = useState(false);
const isGitModEnabled = useGitModEnabled();
const gitConfig = useMemo(
() => (isGitModEnabled ? globalProfile : globalGitConfig),
[isGitModEnabled, globalProfile, globalGitConfig],
);
const [authorName, setAuthorNameInState] = useState(gitConfig?.authorName);
const [authorEmail, setAuthorEmailInState] = useState(gitConfig?.authorEmail);
useEffect(() => {
setIsSaving(false);
setAreFormValuesUpdated(false);
setName(user?.name || "");
setAuthorNameInState(gitConfig?.authorName);
setAuthorEmailInState(gitConfig?.authorEmail);
}, [gitConfig?.authorName, gitConfig?.authorEmail, user?.name]);
useEffect(
function fetchGlobalGitConfigOnInitEffect() {
if (isGitModEnabled) {
fetchGlobalProfile();
} else {
dispatch(fetchGlobalGitConfigInit());
}
},
[dispatch, isGitModEnabled],
);
useEffect(() => {
if (
user?.name !== name ||
gitConfig?.authorName !== authorName ||
gitConfig?.authorEmail !== authorEmail
) {
setAreFormValuesUpdated(true);
} else {
setAreFormValuesUpdated(false);
}
}, [name, authorName, authorEmail]);
const updateConfig = () => {
if (authorName && authorEmail && emailValidator(authorEmail).isValid) {
if (isGitModEnabled) {
updateGlobalProfile({ authorName, authorEmail });
} else {
dispatch(updateGlobalGitConfigInit({ authorName, authorEmail }));
}
} else {
toast.show("Please enter valid git author details", {
kind: "error",
});
setAuthorNameInState(gitConfig?.authorName);
setAuthorEmailInState(gitConfig?.authorEmail);
setIsSaving(false);
}
};
const forgotPassword = async () => {
try {
await forgotPasswordSubmitHandler({ email: user?.email }, dispatch);
toast.show(createMessage(FORGOT_PASSWORD_SUCCESS_TEXT, user?.email), {
kind: "success",
});
dispatch(logoutUser());
} catch (error) {
toast.show((error as { _error: string })._error, {
kind: "error",
});
}
};
const saveName = () => {
const validation = nameValidator(name);
if (!validation.isValid) {
toast.show(validation.message, { kind: "error" });
setIsSaving(false);
setName(user?.name || "");
return;
}
dispatch(
updateUserDetails({
name,
}),
);
};
const onSave = () => {
if (user?.name !== name) {
setIsSaving(true);
saveName();
}
if (
gitConfig?.authorName !== authorName ||
gitConfig?.authorEmail !== authorEmail
) {
setIsSaving(true);
updateConfig();
}
};
const onClear = () => {
setName(user?.name || "");
setAuthorNameInState(gitConfig?.authorName);
setAuthorEmailInState(gitConfig?.authorEmail);
setAreFormValuesUpdated(false);
};
if (user?.email === ANONYMOUS_USERNAME) return null;
return (
<Wrapper>
<SettingsFormWrapper>
<HeaderWrapper>
<SettingsHeader
color="var(--ads-v2-color-fg-emphasis-plus)"
kind="heading-l"
renderAs="h1"
>
Account
</SettingsHeader>
<SettingsSubHeader
color="var(--ads-v2-color-fg-emphasis)"
kind="body-m"
renderAs="h2"
>
Set your profile and git settings
</SettingsSubHeader>
</HeaderWrapper>
<SubCategory kind="heading-s" renderAs="p">
Profile
</SubCategory>
<Flex flexDirection="column" gap="spaces-5">
<FieldWrapper>
<LabelWrapper>
<Text kind="body-m">
{createMessage(USER_DISPLAY_PICTURE_PLACEHOLDER)}
</Text>
</LabelWrapper>
<div className="user-profile-image-picker">
<UserProfileImagePicker />
</div>
</FieldWrapper>
<FieldWrapper>
<Input
data-testid="t--display-name"
isRequired
label={createMessage(USER_DISPLAY_NAME_PLACEHOLDER)}
labelPosition="top"
onChange={setName}
placeholder={createMessage(USER_DISPLAY_NAME_PLACEHOLDER)}
renderAs="input"
size="md"
type="text"
value={name}
/>
</FieldWrapper>
<FieldWrapper>
<Flex flexDirection="column" gap="spaces-2">
<Input
data-testid="t--user-name"
defaultValue={user?.email}
isDisabled
isReadOnly
label={createMessage(USER_EMAIL_PLACEHOLDER)}
labelPosition="top"
placeholder={createMessage(USER_EMAIL_PLACEHOLDER)}
renderAs="input"
size="md"
type="text"
/>
{isFormLoginEnabled && (
<ResetPasswordButton
className="t--user-reset-password"
kind="tertiary"
onClick={forgotPassword}
renderAs="a"
size="md"
startIcon="restart-line"
>
{createMessage(USER_RESET_PASSWORD)}
</ResetPasswordButton>
)}
</Flex>
</FieldWrapper>
</Flex>
<SubCategory kind="heading-s" renderAs="p">
Git author
</SubCategory>
<Flex flexDirection="column" gap="spaces-5">
<FieldWrapper>
{isFetching && <Loader className={Classes.SKELETON} />}
{!isFetching && (
<Input
data-testid="t--git-author-name"
isRequired
label={createMessage(AUTHOR_NAME)}
labelPosition="top"
onChange={setAuthorNameInState}
placeholder={createMessage(AUTHOR_NAME)}
renderAs="input"
size="md"
type="text"
value={authorName}
/>
)}
</FieldWrapper>
<FieldWrapper>
{isFetching && <Loader className={Classes.SKELETON} />}
{!isFetching && (
<Input
data-testid="t--git-author-email"
isRequired
label={createMessage(AUTHOR_EMAIL)}
labelPosition="top"
onChange={setAuthorEmailInState}
placeholder={createMessage(AUTHOR_EMAIL)}
renderAs="input"
size="md"
type="text"
value={authorEmail}
/>
)}
</FieldWrapper>
</Flex>
<SettingsButtonWrapper>
<Button
className="t--admin-settings-save-button"
isDisabled={!areFormValuesUpdated}
isLoading={isSaving}
onClick={onSave}
size="md"
>
{createMessage(SAVE_BUTTON)}
</Button>
<Button
className="t--admin-settings-reset-button"
isDisabled={!areFormValuesUpdated}
kind="secondary"
onClick={onClear}
size="md"
>
{createMessage(RESET_BUTTON)}
</Button>
</SettingsButtonWrapper>
<BottomSpace />
</SettingsFormWrapper>
</Wrapper>
);
};