diff --git a/src/api/endpoints/apiActions.ts b/src/api/endpoints/apiActions.ts index d25d8458..ca5a41c7 100644 --- a/src/api/endpoints/apiActions.ts +++ b/src/api/endpoints/apiActions.ts @@ -1,8 +1,5 @@ import { AxiosRequestConfig } from 'axios'; import { customInstance } from '../../../mock/mutator/customClient'; -import { API_CONFIG } from '../../config'; -import { useCookies } from 'react-cookie' - type SecondParameter any> = Parameters[1]; @@ -42,4 +39,4 @@ export const createGetRequest = (endpoint: string, contentType return response; }); } -} +} \ No newline at end of file diff --git a/src/api/endpoints/apiService.ts b/src/api/endpoints/apiService.ts index 64bb46b8..bba23e99 100644 --- a/src/api/endpoints/apiService.ts +++ b/src/api/endpoints/apiService.ts @@ -16,6 +16,10 @@ export interface RegisterRequest { organization: string } +interface ForgotPasswordReguest { + username: string; +} + type LabelType = | string | { '@value': string; '@language'?: string } @@ -210,6 +214,8 @@ export const retrieveTokenApi = ({ groupname }: { groupname: string }) => { return createGetRequest(endpoint, "application/json")(); }; +export const forgotPassword = createPostRequest(API_CONFIG.REAL_API.USER_RECOVER, { "Content-Type": "application/x-www-form-urlencoded" }) + export const getMatchTerms = async (group: string, term: string, filters = {}) => { try { const response = await createGetRequest(`/${group}/${term}.${BASE_EXTENSION}`, "application/json")(); @@ -244,4 +250,4 @@ export const getTermDiscussions = async (group: string, variantID: string) => { export const getVariant = (group: string, term: string) => { return createGetRequest(`/${group}/variant/${term}`, "application/json")(); -}; \ No newline at end of file +}; diff --git a/src/components/Auth/ForgotPassword.jsx b/src/components/Auth/ForgotPassword.jsx index 16c98db9..7c1662ea 100644 --- a/src/components/Auth/ForgotPassword.jsx +++ b/src/components/Auth/ForgotPassword.jsx @@ -3,17 +3,18 @@ import { Box, Button, FormControl, Grid, Paper, Typography } from "@mui/material import { ArrowBack } from "@mui/icons-material"; import FormField from "./UI/Formfield"; import { Link } from "react-router-dom"; -import { handleForgotPassword } from "../../api/endpoints/index"; +import { forgotPassword } from "../../api/endpoints/apiService"; const ForgotPassword = () => { - const [email, setEmail] = React.useState(""); + const [username, setUsername] = React.useState(""); + const [error, setError] = React.useState(""); - const forgotPassword = async () => { + const handleForgotPassword = async () => { try { - await handleForgotPassword(email); - console.log("Password reset email sent"); + await forgotPassword({username: username}); } catch (error) { console.error("Error:", error); + setError(error.message); } }; @@ -29,14 +30,15 @@ const ForgotPassword = () => {
setEmail(e.target.value)} + label="Username" + placeholder="Enter your username" + value={username} + onChange={(e) => setUsername(e.target.value)} + errorMessage={error} /> - diff --git a/src/components/Dashboard/User/AccountSettingsDialog.jsx b/src/components/Dashboard/User/AccountSettingsDialog.jsx new file mode 100644 index 00000000..abca8238 --- /dev/null +++ b/src/components/Dashboard/User/AccountSettingsDialog.jsx @@ -0,0 +1,205 @@ +import PropTypes from "prop-types"; +import { useState } from "react"; +import { Box, Divider, Button, Typography, Avatar, Stack, Grid, TextField, Link } from "@mui/material"; +import CustomizedDialog from "../../common/CustomizedDialog"; +import PasswordField from "./PasswordField"; +import PersonOutlineIcon from '@mui/icons-material/PersonOutline'; +import ModeEditOutlineOutlinedIcon from "@mui/icons-material/ModeEditOutlineOutlined"; +import SaveOutlinedIcon from '@mui/icons-material/SaveOutlined'; +import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; + +import { vars } from "../../../theme/variables"; +const { gray600, gray700, brand700 } = vars; + +const HeaderRightSideContent = ({ handleClose, handleSubmit }) => { + return ( + + + + + ); +}; + +HeaderRightSideContent.propTypes = { + handleClose: PropTypes.func.isRequired, + handleSubmit: PropTypes.func.isRequired, +}; + +const AccountSettingsDialog = ({ + user, + open, + handleClose +}) => { + const [showPasswordField, setShowPasswordField] = useState(false); + const [formData, setFormData] = useState({ + email: user?.email, + currentPassword: "", + newPassword: "", + confirmPassword: "", + }) + + const handleInputChange = (field, value) => { + setFormData((prev) => ({ + ...prev, + [field]: value, + })) + } + + const handleSubmit = (e) => { + e.preventDefault() + console.log("Form submitted:", formData) + } + + const userGroupname = user?.groupname.charAt(0).toUpperCase() + user?.groupname.slice(1) + const isPasswordFormValid = formData.currentPassword.trim() !== "" && formData.newPassword.trim() !== "" && formData.confirmPassword.trim() !== ""; + + + return ( + } + sx={{ + '& .MuiDialog-paper': { + maxWidth: '62.5rem' + } + }} + > + + + + + + {userGroupname} + {user?.email} + + + + + + + + + Email + + handleInputChange("email", e.target.value)} + disabled + size="small" + /> + + Email address can't be changed from the interface. Please contact us to make this change. + + Contact support + + + + + + + {showPasswordField && ( + <> + + + + Current Password + + handleInputChange("currentPassword", e.target.value)} + placeholder="Enter your current password" + /> + + + + + + + Enter New Password + + handleInputChange("newPassword", e.target.value)} + placeholder="Enter new password" + /> + + + + + + + Confirm New Password + + handleInputChange("confirmPassword", e.target.value)} + placeholder="Confirm new password" + /> + + + + )} + + + + {!showPasswordField ? ( + + ) : (<> + + + )} + + + + + + ); +}; + +AccountSettingsDialog.propTypes = { + user: PropTypes.object.isRequired, + open: PropTypes.bool.isRequired, + handleClose: PropTypes.func.isRequired, +}; + +export default AccountSettingsDialog; diff --git a/src/components/Dashboard/User/PasswordField.jsx b/src/components/Dashboard/User/PasswordField.jsx new file mode 100644 index 00000000..6da60e95 --- /dev/null +++ b/src/components/Dashboard/User/PasswordField.jsx @@ -0,0 +1,52 @@ +import { useState } from "react"; +import { Button, Typography } from "@mui/material"; +import ActionInput from "../../common/ActionInput"; +import { VisibilityOutlined, VisibilityOffOutlined } from "@mui/icons-material" +import PropTypes from "prop-types"; + +const PasswordField = ({ value, name, placeholder, handleChange, error, helperText }) => { + const [showPassword, setShowPassword] = useState(false) + + return ( +
+ setShowPassword(!showPassword)} + startIcon={showPassword ? : } + > + {showPassword ? "Show" : "Hide"} + + } + /> + {helperText && ( + + {helperText} + + )} + {error && {`${error.charAt(0).toUpperCase() + error.slice(1)}`}} +
+ ) +} + +PasswordField.propTypes = { + placeholder: PropTypes.string, + name: PropTypes.string.isRequired, + value: PropTypes.string.isRequired, + error: PropTypes.bool, + helperText: PropTypes.string, + handleChange: PropTypes.func.isRequired +}; + +PasswordField.defaultProps = { + placeholder: "", +}; + + +export default PasswordField; \ No newline at end of file diff --git a/src/components/Dashboard/User/index.jsx b/src/components/Dashboard/User/index.jsx index 721e41a6..fb52c178 100644 --- a/src/components/Dashboard/User/index.jsx +++ b/src/components/Dashboard/User/index.jsx @@ -1,10 +1,12 @@ -import {Box, Button, Chip, Grid, Stack, Typography} from "@mui/material"; +import { useState } from "react"; +import { Box, Button, Chip, Grid, Stack, Typography } from "@mui/material"; import { vars } from "../../../theme/variables"; import CustomBreadcrumbs from "../../common/CustomBreadcrumbs"; import HomeOutlinedIcon from "@mui/icons-material/HomeOutlined"; import { SettingsOutlined } from "@mui/icons-material"; -import {GlobalDataContext} from "../../../contexts/DataContext"; +import { GlobalDataContext } from "../../../contexts/DataContext"; import { useContext } from "react"; +import AccountSettingsDialog from "./AccountSettingsDialog"; const { gray25, gray600, gray800, gray500 } = vars; @@ -14,7 +16,8 @@ const breadcrumbItems = [ ]; const User = () => { - const {user} = useContext(GlobalDataContext) + const { user } = useContext(GlobalDataContext) + const [open, setOpen] = useState(false); return ( { @@ -69,6 +73,7 @@ const User = () => {
+ setOpen(false)} /> ); }; diff --git a/src/components/SingleOrganization/CreateForkDialog.jsx b/src/components/SingleOrganization/CreateForkDialog.jsx index d2227bc5..5f2120b8 100644 --- a/src/components/SingleOrganization/CreateForkDialog.jsx +++ b/src/components/SingleOrganization/CreateForkDialog.jsx @@ -103,7 +103,7 @@ const CreateForkDialog = ({ open, handleClose, onSubmit }) => { > - Add a fork + Add a fork { - To the organization + To the organization { + return ( + + + {inputIcon && ( + {inputIcon} + )} + {actionButton} + + ); +} + +ActionInput.propTypes = { + placeholder: PropTypes.string, + name: PropTypes.string.isRequired, + value: PropTypes.string.isRequired, + error: PropTypes.bool, + onChange: PropTypes.func.isRequired, + type: PropTypes.string, + size: PropTypes.string, + actionButton: PropTypes.node, + inputIcon: PropTypes.element +}; + +ActionInput.defaultProps = { + placeholder: "", +}; + + +export default ActionInput \ No newline at end of file diff --git a/src/components/common/CustomizedDialog.jsx b/src/components/common/CustomizedDialog.jsx index ded95122..41f9d946 100644 --- a/src/components/common/CustomizedDialog.jsx +++ b/src/components/common/CustomizedDialog.jsx @@ -19,6 +19,7 @@ const CustomizedDialog = ({ title, open, handleClose, + fullScreen = true, HeaderRightSideContent, sx, }) => { @@ -27,7 +28,7 @@ const CustomizedDialog = ({ onClose={handleClose} aria-labelledby="customized-dialog-title" open={open} - fullScreen + fullScreen={fullScreen} TransitionComponent={Transition} sx={sx} > @@ -85,6 +86,7 @@ CustomizedDialog.propTypes = { title: PropTypes.string.isRequired, open: PropTypes.bool.isRequired, handleClose: PropTypes.func.isRequired, + fullScreen: PropTypes.bool, HeaderRightSideContent: PropTypes.node, sx: PropTypes.object, }; diff --git a/src/config.js b/src/config.js index d280e79f..1e92e581 100644 --- a/src/config.js +++ b/src/config.js @@ -35,6 +35,7 @@ export const API_CONFIG = { API_RETRIEVE_TOKEN: "/priv/api-tokens", GET_ORGANIZATIONS: "/priv/role-other", LOGOUT: "/priv/logout", + USER_RECOVER: "/u/ops/user-recover" }, SESSION_DATA: { SETTINGS: "settings", diff --git a/src/theme/index.jsx b/src/theme/index.jsx index b166478a..7d5fb139 100644 --- a/src/theme/index.jsx +++ b/src/theme/index.jsx @@ -113,6 +113,21 @@ const theme = createTheme({ } `, }, + MuiTypography: { + styleOverrides: { + h6: { + fontSize: '1.125rem', + fontWeight: 600 + } + } + }, + MuiDivider: { + styleOverrides: { + root: { + borderColor: gray200 + } + } + }, MuiRichTreeView: { styleOverrides: { backgroundColor: "red", @@ -197,6 +212,7 @@ const theme = createTheme({ MuiOutlinedInput: { styleOverrides: { root: { + borderRadius: "0.5rem", "& .MuiOutlinedInput-notchedOutline": { borderColor: gray200, }, @@ -221,6 +237,14 @@ const theme = createTheme({ color: error500 } }, + '&.Mui-disabled': { + background: gray50, + WebkitTextFillColor: `${gray500} !important` + } + }, + sizeSmall: { + padding: '0.5rem 0.75rem', + '& input': { padding: 0 } } }, }, @@ -619,6 +643,9 @@ const theme = createTheme({ height: "1.25rem", fontSize: "1.25rem", }, + fontSizeLarge: { + fontSize: '2rem' + } }, }, MuiAccordion: { @@ -1037,7 +1064,7 @@ const theme = createTheme({ }, }, }, - }, + } }, }); diff --git a/src/theme/variables.js b/src/theme/variables.js index 648a1927..40a108e5 100644 --- a/src/theme/variables.js +++ b/src/theme/variables.js @@ -73,5 +73,7 @@ export const vars = { success900: '#074D31', success950: '#053321', paperShadow: '0px 12px 16px -4px rgba(16, 24, 40, 0.08), 0px 4px 6px -2px rgba(16, 24, 40, 0.03)', - errorInputBoxShadow: '0px 1px 2px 0px rgba(16, 24, 40, 0.05), 0px 0px 0px 4px rgba(240, 68, 56, 0.24)' + errorInputBoxShadow: '0px 1px 2px 0px rgba(16, 24, 40, 0.05), 0px 0px 0px 4px rgba(240, 68, 56, 0.24)', + inputBoxShadow: '0px 1px 2px 0px rgba(16, 24, 40, 0.05)', + inputErrorBoxShadow: '0px 0px 0px 4px rgba(240, 68, 56, 0.24)' } \ No newline at end of file