Merge pull request #157 from bluewave-labs/feat/profile-settings

Implemented Profile settings design with some limited functionality (Client side), #143
This commit is contained in:
Daniel C
2024-06-19 19:42:34 -04:00
committed by GitHub
9 changed files with 1268 additions and 24 deletions
+1 -14
View File
@@ -18,25 +18,12 @@ const levelConfig = {
variant: "contained", variant: "contained",
color: "error", color: "error",
}, },
imagePrimary: {
color: "primary",
variant: "contained",
},
imageSecondary: {
color: "secondary",
variant: "outlined",
},
imageTertiary: {
color: "tertiary",
variant: "text",
},
}; };
/** /**
* ButtonSpinner component displays a button with loading loadingText capability.
* @component * @component
* @param {Object} props * @param {Object} props
* @param {'primary' | 'secondary' | 'tertiary' | 'error' | 'imagePrimary' | 'imageSecondary' | 'imageTertiary'} props.level - The style level of the button. * @param {'primary' | 'secondary' | 'tertiary' | 'error'} props.level - The style level of the button.
* @param {string} props.label - The label text displayed on the button. * @param {string} props.label - The label text displayed on the button.
* @param {React.ReactNode} [props.img] - Icon or image element to display within the button. * @param {React.ReactNode} [props.img] - Icon or image element to display within the button.
* @param {boolean} [props.disabled=false] - Determines if the button is disabled. * @param {boolean} [props.disabled=false] - Determines if the button is disabled.
@@ -0,0 +1,183 @@
import TabPanel from "@mui/lab/TabPanel";
import React, { useState } from "react";
import AnnouncementsDualButtonWithIcon from "../../Announcements/AnnouncementsDualButtonWithIcon/AnnouncementsDualButtonWithIcon";
import { useTheme } from "@emotion/react";
import {
Box,
Divider,
FormControl,
IconButton,
InputAdornment,
OutlinedInput,
Stack,
Typography,
} from "@mui/material";
import VisibilityOff from "@mui/icons-material/VisibilityOff";
import Visibility from "@mui/icons-material/Visibility";
import InfoOutlinedIcon from "@mui/icons-material/InfoOutlined";
import WarningAmberOutlinedIcon from "@mui/icons-material/WarningAmberOutlined";
import ButtonSpinner from "../../ButtonSpinner";
/**
* PasswordPanel component manages the form for editing password.
*
* @returns {JSX.Element}
*/
const PasswordPanel = () => {
const theme = useTheme();
//TODO - use redux loading state
//!! - currently all loading buttons are tied to the same state
const [isLoading, setIsLoading] = useState(false);
const [showPassword, setShowPassword] = useState(false);
//TODO - implement save password function
const handleSavePassword = () => {
setIsLoading(true);
setTimeout(() => {
setIsLoading(false);
setIsOpen(false);
}, 2000);
};
return (
<TabPanel
value="1"
sx={{ padding: "0", marginTop: theme.spacing(6.25), width: "100%" }}
>
<form className="edit-password-form" noValidate spellCheck="false">
<div className="edit-password-form__wrapper">
<AnnouncementsDualButtonWithIcon
icon={<InfoOutlinedIcon style={{ fill: "#344054" }} />}
subject="SSO login"
body="Since you logged in via SSO, you cannot reset or modify your password."
/>
</div>
<div className="edit-password-form__wrapper">
<Stack
direction="column"
gap="8px"
sx={{ flex: 1, marginRight: "10px" }}
>
<Typography variant="h4" component="h1">
Current Password
</Typography>
</Stack>
<FormControl sx={{ flex: 1, minWidth: theme.spacing(30) }}>
<OutlinedInput
id="edit-current-password"
value="RandomPasswordLol"
type={showPassword ? "text" : "password"}
endAdornment={
<InputAdornment position="end">
<IconButton
aria-label="toggle password visibility"
onClick={() => setShowPassword((show) => !show)}
sx={{
width: "30px",
height: "30px",
"&:focus": {
outline: "none",
},
}}
>
{!showPassword ? (
<VisibilityOff sx={{ fill: "#98A2B3" }} />
) : (
<Visibility sx={{ fill: "#98A2B3" }} />
)}
</IconButton>
</InputAdornment>
}
></OutlinedInput>
</FormControl>
</div>
<div className="edit-password-form__wrapper">
<Stack
direction="column"
gap="8px"
sx={{ flex: 1, marginRight: "10px" }}
>
<Typography variant="h4" component="h1">
Password
</Typography>
</Stack>
<FormControl sx={{ flex: 1, minWidth: theme.spacing(30) }}>
<OutlinedInput
id="edit-password"
value="RandomPasswordLol"
type={showPassword ? "text" : "password"}
endAdornment={
<InputAdornment position="end">
<IconButton
aria-label="toggle password visibility"
onClick={() => setShowPassword((show) => !show)}
sx={{
width: "30px",
height: "30px",
"&:focus": {
outline: "none",
},
}}
>
{!showPassword ? (
<VisibilityOff sx={{ fill: "#98A2B3" }} />
) : (
<Visibility sx={{ fill: "#98A2B3" }} />
)}
</IconButton>
</InputAdornment>
}
></OutlinedInput>
</FormControl>
</div>
<div className="edit-password-form__wrapper">
<Stack
direction="column"
gap="8px"
sx={{ flex: 1, marginRight: "10px" }}
>
<Typography variant="h4" component="h1">
Confirm new password
</Typography>
</Stack>
<Box sx={{ flex: 1, minWidth: theme.spacing(30) }}>
<AnnouncementsDualButtonWithIcon
icon={<WarningAmberOutlinedIcon style={{ fill: "#f79009" }} />}
body="New password must contain at least 8 characters and must have at least one uppercase letter, one number and one symbol."
/>
</Box>
</div>
<Divider
aria-hidden="true"
width="0"
sx={{ marginY: theme.spacing(6.25) }}
/>
<Stack direction="row" justifyContent="flex-end">
<Box width="fit-content">
<ButtonSpinner
level="primary"
label="Save"
onClick={handleSavePassword}
isLoading={isLoading}
loadingText="Saving..."
sx={{
paddingX: "40px",
height: "fit-content",
fontSize: "13px",
"&:focus": {
outline: "none",
},
}}
/>
</Box>
</Stack>
</form>
</TabPanel>
);
};
PasswordPanel.propTypes = {
// No props are being passed to this component, hence no specific PropTypes are defined.
};
export default PasswordPanel;
@@ -0,0 +1,294 @@
import { useTheme } from "@emotion/react";
import { useState } from "react";
import TabPanel from "@mui/lab/TabPanel";
import {
Avatar,
Box,
Divider,
Modal,
Stack,
TextField,
Typography,
} from "@mui/material";
import ButtonSpinner from "../../ButtonSpinner";
import Button from "../../Button";
/**
* ProfilePanel component displays a form for editing user profile information
* and allows for actions like updating profile picture, credentials,
* and deleting account.
*
* @returns {JSX.Element}
*/
const ProfilePanel = () => {
const theme = useTheme();
//TODO - use redux loading state
//!! - currently all loading buttons are tied to the same state
const [isLoading, setIsLoading] = useState(false);
//TODO - implement delete profile picture function
const handleDeletePicture = () => {
setIsLoading(true);
setTimeout(() => {
setIsLoading(false);
}, 2000);
};
//TODO - implement update profile function
const handleUpdatePicture = () => {};
const [isOpen, setIsOpen] = useState(false);
//TODO - implement delete account function
const handleDeleteAccount = () => {
setIsLoading(true);
setTimeout(() => {
setIsLoading(false);
setIsOpen(false);
}, 2000);
};
//TODO - implement save profile function
const handleSaveProfile = () => {
setIsLoading(true);
setTimeout(() => {
setIsLoading(false);
setIsOpen(false);
}, 2000);
};
return (
<TabPanel
value="0"
sx={{ padding: "0", marginTop: theme.spacing(6.25), width: "100%" }}
>
<form className="edit-profile-form" noValidate spellCheck="false">
<div className="edit-profile-form__wrapper">
<Stack
direction="column"
gap="8px"
sx={{ flex: 1, marginRight: "10px" }}
>
<Typography variant="h4" component="h1">
First Name
</Typography>
</Stack>
{/* TODO - use existing textfield components */}
<TextField
id="edit-first-name"
placeholder="Enter your first name"
sx={{
flex: 1,
minWidth: theme.spacing(30),
}}
/>
</div>
<div className="edit-profile-form__wrapper">
<Stack
direction="column"
gap="8px"
sx={{ flex: 1, marginRight: "10px" }}
>
<Typography variant="h4" component="h1">
Last Name
</Typography>
</Stack>
{/* TODO - use existing textfield components */}
<TextField
id="edit-last-name"
placeholder="Enter your last name"
sx={{
flex: 1,
minWidth: theme.spacing(30),
}}
/>
</div>
<div className="edit-profile-form__wrapper">
<Stack
direction="column"
gap="8px"
sx={{ flex: 1, marginRight: "10px" }}
>
<Typography variant="h4" component="h1">
Email
</Typography>
<Typography variant="h5" component="p">
After updating, you'll receive a confirmation email.
</Typography>
</Stack>
{/* TODO - use existing textfield components */}
<TextField
id="edit-email"
placeholder="Enter your email"
sx={{
flex: 1,
minWidth: theme.spacing(30),
}}
/>
</div>
<div className="edit-profile-form__wrapper">
<Stack
direction="column"
gap="8px"
sx={{ flex: 1, marginRight: "10px" }}
>
<Typography variant="h4" component="h1">
Your Photo
</Typography>
<Typography variant="h5" component="p">
This photo will be displayed in your profile page.
</Typography>
</Stack>
<Stack direction="row" alignItems="center" sx={{ flex: 1 }}>
{/* TODO - Use Avatar component instead of @mui */}
<Avatar
alt="Remy Sharp"
src="/static/images/avatar/2.jpg"
className="icon-button-avatar"
style={{ width: "64px", height: "64px" }}
/>
<ButtonSpinner
level="tertiary"
label="Delete"
onClick={handleDeletePicture}
isLoading={isLoading}
sx={{
height: "fit-content",
fontSize: "13px",
"&:focus": {
outline: "none",
},
}}
/>
{/* TODO - modal popup for update pfp? */}
<Button
level="tertiary"
label="Update"
onClick={handleUpdatePicture}
sx={{
height: "fit-content",
color: theme.palette.primary.main,
fontSize: "13px",
"&:focus": {
outline: "none",
},
}}
/>
</Stack>
</div>
</form>
<Divider aria-hidden="true" sx={{ marginY: theme.spacing(6.25) }} />
<form className="delete-profile-form" noValidate spellCheck="false">
<div className="delete-profile-form__wrapper">
<Stack direction="column" gap="15px">
<Typography variant="h4" component="h1">
Delete account
</Typography>
<Typography variant="h5" component="p">
Note that deleting your account will remove all data from our
system. This is permanent and non-recoverable.
</Typography>
<Box sx={{ mt: theme.spacing(1) }}>
<Button
level="error"
label="Delete account"
onClick={() => setIsOpen(true)}
sx={{
fontSize: "13px",
"&:focus": {
outline: "none",
},
}}
/>
</Box>
</Stack>
</div>
</form>
<Divider
aria-hidden="true"
width="0"
sx={{ marginY: theme.spacing(6.25) }}
/>
<Stack direction="row" justifyContent="flex-end">
<Box width="fit-content">
<ButtonSpinner
level="primary"
label="Save"
onClick={handleSaveProfile}
isLoading={isLoading}
loadingText="Saving..."
sx={{
paddingX: "40px",
height: "fit-content",
fontSize: "13px",
"&:focus": {
outline: "none",
},
}}
/>
</Box>
</Stack>
{/* TODO - Update ModalPopup Component with @mui for reusability */}
<Modal
aria-labelledby="modal-delete-account"
aria-describedby="delete-account-confirmation"
open={isOpen}
onClose={() => setIsOpen(false)}
disablePortal
>
<Stack
gap="10px"
sx={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: 400,
bgcolor: "white",
border: "solid 1px #f2f2f2",
borderRadius: "4px",
boxShadow: 24,
p: "30px",
"&:focus": {
outline: "none",
},
}}
>
<Typography id="modal-delete-account" variant="h4" component="h1">
Really delete this account?
</Typography>
<Typography
id="delete-account-confirmation"
variant="h5"
component="p"
sx={{
color: theme.palette.secondary.main,
fontSize: "13px",
}}
>
If you delete your account, you will no longer be able to sign in,
and all of your data will be deleted. Deleting your account is
permanent and non-recoverable action.
</Typography>
<Stack direction="row" gap="10px" mt="10px" justifyContent="flex-end">
<Button
level="tertiary"
label="Cancel"
onClick={() => setIsOpen(false)}
sx={{ fontSize: "13px" }}
/>
<ButtonSpinner
level="error"
label="Delete account"
onClick={handleDeleteAccount}
isLoading={isLoading}
sx={{ fontSize: "13px" }}
/>
</Stack>
</Stack>
</Modal>
</TabPanel>
);
};
ProfilePanel.propTypes = {
// No props are being passed to this component, hence no specific PropTypes are defined.
};
export default ProfilePanel;
@@ -0,0 +1,569 @@
import { useTheme } from "@emotion/react";
import TabPanel from "@mui/lab/TabPanel";
import {
Box,
Checkbox,
Container,
Divider,
MenuItem,
Modal,
Select,
Stack,
Table,
TableBody,
TableCell,
TableHead,
TableRow,
TextField,
Typography,
} from "@mui/material";
import ButtonSpinner from "../../ButtonSpinner";
import Button from "../../Button";
import { useState } from "react";
/**
* TeamPanel component manages the organization and team members,
* providing functionalities like renaming the organization, managing team members,
* and inviting new members.
*
* @returns {JSX.Element}
*/
const teamColumns = [
{
id: "checkbox",
label: "",
sx: { minWidth: "20px", width: "40px" },
},
{
id: "name",
label: "NAME",
sx: { fontSize: "12px" },
},
{ id: "email", label: "EMAIL", sx: { fontSize: "12px" } },
{ id: "role", label: "ROLE", sx: { fontSize: "12px" } },
];
//for testing, will be removed later
const teamConfig = [
{
id: 0,
isChecked: false,
name: "John Connor",
email: "john@domain.com",
type: "admin",
role: "Administrator",
createdAt: "10/4/2022",
},
{
id: 1,
isChecked: false,
name: "Adam McFadden",
email: "adam@domain.com",
type: "member",
role: "Member",
createdAt: "10/4/2022",
},
{
id: 2,
isChecked: false,
name: "Cris Cross",
email: "cris@domain.com",
type: "member",
role: "Member",
createdAt: "10/4/2022",
},
{
id: 3,
isChecked: false,
name: "Prince",
email: "prince@domain.com",
type: "member",
role: "Member",
createdAt: "10/4/2022",
},
];
const actionsConfig = [
{
value: "bulk",
label: "Bulk actions",
},
];
const roleConfig = [
{
value: "role",
label: "Change role to",
},
];
const TeamPanel = () => {
const theme = useTheme();
//TODO - use redux loading state
//!! - currently all loading buttons are tied to the same state
const [isLoading, setIsLoading] = useState(false);
const [orgStates, setOrgStates] = useState({
name: "Bluewave Labs",
isLoading: false,
isOpen: false,
newName: "",
});
const toggleOrgModal = (state) => {
setOrgStates((prev) => ({
...prev,
isOpen: state,
}));
};
const toggleOrgLoading = (state) => {
setOrgStates((prev) => ({
...prev,
isLoading: state,
}));
};
const handleRenameOrg = () => {
toggleOrgLoading(true);
setTimeout(() => {
setOrgStates((prev) => ({
...prev,
name: prev.newName !== "" ? prev.newName : prev.name,
isLoading: false,
isOpen: false,
newName: "",
}));
}, 2000);
};
const [teamStates, setTeamStates] = useState({
members: teamConfig,
filter: "",
});
const handleCheckCell = (id) => {
const updatedTeamStates = [...teamStates.members];
updatedTeamStates[id] = {
...updatedTeamStates[id],
isChecked: !updatedTeamStates[id].isChecked,
};
setTeamStates((prev) => ({
...prev,
members: updatedTeamStates,
}));
};
const handleFilter = (filter) => {
setTeamStates((prev) => ({
...prev,
filter: filter,
}));
};
//TODO - implement select action function
const handleSelectActionType = () => {
setIsLoading(true);
setTimeout(() => {
setIsLoading(false);
}, 2000);
};
//TODO - implement select role function
const handleSelectRoleType = () => {
setIsLoading(true);
setTimeout(() => {
setIsLoading(false);
}, 2000);
};
//TODO - implement save team function
const handleSaveTeam = () => {
setIsLoading(true);
setTimeout(() => {
setIsLoading(false);
}, 2000);
};
const [toggleInviteModal, setToggleInviteModal] = useState(false);
const handleInviteMember = () => {};
const handleMembersQuery = (type) => {
let count = 0;
teamStates.members.forEach((member) => {
type === "" ? count++ : member.type === type ? count++ : "";
});
return count;
};
return (
<TabPanel
value="2"
sx={{ padding: "0", marginTop: theme.spacing(6.25), width: "100%" }}
>
<form className="edit-team-form" noValidate spellCheck="false">
<div className="edit-team-form__wrapper">
<Stack
direction="column"
gap="8px"
sx={{ flex: 1, marginRight: "10px" }}
>
<Typography variant="h4" component="h1">
Organization name
</Typography>
</Stack>
<Stack
id="org-name-flex-container"
direction="row"
justifyContent="flex-end"
gap="8px"
sx={{ flex: 1 }}
>
<ButtonSpinner
level="tertiary"
label={!orgStates.isLoading ? orgStates.name : ""}
disabled
onClick={() => toggleOrgModal(true)}
isLoading={orgStates.isLoading}
sx={{ fontSize: "13px" }}
/>
<Button
level="primary"
label="Rename"
sx={{ paddingX: "30px", fontSize: "13px" }}
onClick={() => toggleOrgModal(true)}
/>
</Stack>
</div>
<div className="edit-team-form__wrapper">
<Typography variant="h4" component="h1">
Team members
</Typography>
</div>
<div
className="edit-team-form__wrapper compact"
style={{ alignItems: "center" }}
>
<Stack
direction="row"
gap="20px"
alignItems="center"
sx={{ flex: 1, fontSize: "14px" }}
>
<Box onClick={() => handleFilter("")} sx={{ cursor: "pointer" }}>
All
<span className="members-query">
<span>{handleMembersQuery("")}</span>
</span>
</Box>
<Box
onClick={() => handleFilter("admin")}
sx={{ cursor: "pointer" }}
>
Administrator
<span className="members-query">
<span>{handleMembersQuery("admin")}</span>
</span>
</Box>
<Box
onClick={() => handleFilter("member")}
sx={{ cursor: "pointer" }}
>
Member
<span className="members-query">
<span>{handleMembersQuery("member")}</span>
</span>
</Box>
</Stack>
<Button
level="primary"
label="Invite a team member"
sx={{ paddingX: "30px", fontSize: "13px" }}
onClick={() => setToggleInviteModal(true)}
/>
</div>
<div className="edit-team-form__wrapper compact">
<Container
disableGutters
sx={{
border: `1px solid ${theme.palette.section.borderColor}`,
borderRadius: `${theme.shape.borderRadius}px`,
borderBottom: "none",
}}
>
<Stack direction="row" gap="40px" p="20px">
<Stack direction="row" gap="10px" alignItems="center">
<Select
id="select-actions"
value="bulk"
sx={{
fontSize: "13px",
color: theme.palette.secondary.main,
}}
inputProps={{ id: "select-actions-input" }}
>
{actionsConfig.map((action) => (
<MenuItem
value={action.value}
key={action.value}
sx={{ fontSize: "13px" }}
>
{action.label}
</MenuItem>
))}
</Select>
<ButtonSpinner
level="secondary"
label="Apply"
onClick={handleSelectActionType}
isLoading={isLoading}
sx={{
height: "fit-content",
fontSize: "13px",
fontWeight: 500,
bgcolor: "#fafafa",
}}
/>
</Stack>
<Stack direction="row" gap="10px" alignItems="center">
<Select
id="select-role"
value="role"
sx={{
fontSize: "13px",
color: theme.palette.secondary.main,
}}
inputProps={{ id: "select-role-input" }}
>
{roleConfig.map((role) => (
<MenuItem
value={role.value}
key={role.value}
sx={{ fontSize: "13px" }}
>
{role.label}
</MenuItem>
))}
</Select>
<ButtonSpinner
level="secondary"
label="Apply"
onClick={handleSelectRoleType}
isLoading={isLoading}
sx={{
height: "fit-content",
fontSize: "13px",
fontWeight: 500,
bgcolor: "#fafafa",
}}
/>
</Stack>
</Stack>
<Table
sx={{
borderTop: `1px solid ${theme.palette.section.borderColor}`,
tableLayout: "fixed",
}}
>
<TableHead>
<TableRow
sx={{
bgcolor: "#fafafa",
}}
>
{teamColumns.map((cell) => (
<TableCell
key={cell.id}
sx={{
...cell.sx,
color: theme.palette.otherColors.slateGray,
}}
>
{cell.label}
</TableCell>
))}
</TableRow>
</TableHead>
<TableBody>
{teamStates.members.map((cell) =>
teamStates.filter === "" ||
teamStates.filter === cell.type ? (
<TableRow key={cell.id}>
<TableCell align="center">
<Checkbox
id={`${cell.id}-${cell.name}`}
checked={cell.isChecked}
onChange={() => handleCheckCell(cell.id)}
inputProps={{ "aria-label": "controlled" }}
/>
</TableCell>
<TableCell>
<Stack direction="column">
<Box
sx={{
color: theme.palette.otherColors.blackish,
verticalAlign: "top",
}}
>
{cell.name}
</Box>
<Box>Created at {cell.createdAt}</Box>
</Stack>
</TableCell>
<TableCell>{cell.email}</TableCell>
<TableCell>{cell.role}</TableCell>
</TableRow>
) : (
""
)
)}
</TableBody>
</Table>
</Container>
</div>
<Divider
aria-hidden="true"
width="0"
sx={{ marginY: theme.spacing(6.25) }}
/>
<Stack direction="row" justifyContent="flex-end">
<Box width="fit-content">
<ButtonSpinner
level="primary"
label="Save"
onClick={handleSaveTeam}
isLoading={isLoading}
loadingText="Saving..."
sx={{
paddingX: "40px",
height: "fit-content",
fontSize: "13px",
"&:focus": {
outline: "none",
},
}}
/>
</Box>
</Stack>
</form>
<Modal
aria-labelledby="modal-edit-org-name"
aria-describedby="edit-organization-name"
open={orgStates.isOpen}
onClose={() => toggleOrgModal(false)}
disablePortal
>
<Stack
gap="20px"
sx={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: 400,
bgcolor: "white",
border: "solid 1px #f2f2f2",
borderRadius: "4px",
boxShadow: 24,
p: "30px",
"&:focus": {
outline: "none",
},
}}
>
<Typography id="modal-edit-org-name" variant="h4" component="h1">
Rename this organization?
</Typography>
<TextField
id="edit-organization-name"
placeholder={orgStates.name}
spellCheck="false"
value={orgStates.newName}
onChange={(event) =>
setOrgStates((prev) => ({
...prev,
newName: event.target.value,
}))
}
></TextField>
<Stack direction="row" gap="10px" mt="10px" justifyContent="flex-end">
<Button
level="tertiary"
label="Cancel"
onClick={() => toggleOrgModal(false)}
sx={{ fontSize: "13px" }}
/>
<ButtonSpinner
level="primary"
label="Rename"
onClick={handleRenameOrg}
isLoading={orgStates.isLoading}
sx={{ fontSize: "13px", paddingX: "30px" }}
/>
</Stack>
</Stack>
</Modal>
<Modal
aria-labelledby="modal-invite-member"
aria-describedby="invite-member-to-team"
open={toggleInviteModal}
onClose={() => setToggleInviteModal(false)}
disablePortal
>
<Stack
gap="10px"
sx={{
position: "absolute",
top: "50%",
left: "50%",
transform: "translate(-50%, -50%)",
width: 400,
bgcolor: "white",
border: "solid 1px #f2f2f2",
borderRadius: "4px",
boxShadow: 24,
p: "30px",
"&:focus": {
outline: "none",
},
}}
>
<Typography id="modal-invite-member" variant="h4" component="h1">
Invite new team member
</Typography>
<Typography
id="invite-member-to-team"
variant="h5"
component="p"
sx={{
color: theme.palette.secondary.main,
fontSize: "13px",
}}
>
When you add a new team member, they will get access to all
monitors.
</Typography>
<TextField
id="input-team-member"
spellCheck="false"
// value={orgStates.newName}
// onChange={(event) =>
// setOrgStates((prev) => ({
// ...prev,
// newName: event.target.value,
// }))
// }
></TextField>
<Stack direction="row" gap="10px" mt="10px" justifyContent="flex-end">
<Button
level="tertiary"
label="Cancel"
onClick={() => setToggleInviteModal(false)}
sx={{ fontSize: "13px" }}
/>
<ButtonSpinner
level="primary"
label="Send invite"
onClick={handleInviteMember}
isLoading={isLoading}
sx={{ fontSize: "13px" }}
/>
</Stack>
</Stack>
</Modal>
</TabPanel>
);
};
TeamPanel.propTypes = {
// No props are being passed to this component, hence no specific PropTypes are defined.
};
export default TeamPanel;
+2 -2
View File
@@ -390,7 +390,7 @@ const Demo = () => {
isLoading={isLoading} isLoading={isLoading}
/> />
<ButtonSpinner <ButtonSpinner
level="imagePrimary" level="primary"
label="Upload" label="Upload"
position="start" position="start"
img={<UploadIcon/>} img={<UploadIcon/>}
@@ -398,7 +398,7 @@ const Demo = () => {
isLoading={isLoading} isLoading={isLoading}
/> />
<ButtonSpinner <ButtonSpinner
level="imageSecondary" level="secondary"
label="Send" label="Send"
position="end" position="end"
img={<SendIcon/>} img={<SendIcon/>}
+133
View File
@@ -0,0 +1,133 @@
.edit-profile-form,
.edit-password-form,
.delete-profile-form,
.edit-team-form {
width: inherit;
}
.edit-profile-form__wrapper,
.delete-profile-form__wrapper {
width: 90%;
}
.edit-profile-form__wrapper,
.delete-profile-form__wrapper,
.edit-password-form__wrapper,
.edit-team-form__wrapper {
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: flex-start;
}
.edit-profile-form__wrapper h1.MuiTypography-root,
.delete-profile-form__wrapper h1.MuiTypography-root,
.edit-password-form__wrapper h1.MuiTypography-root,
.edit-team-form__wrapper h1.MuiTypography-root {
font-size: var(--env-var-font-size-medium);
color: var(--env-var-color-2);
font-weight: 600;
}
.edit-profile-form__wrapper p.MuiTypography-root,
.delete-profile-form__wrapper p.MuiTypography-root {
font-size: var(--env-var-font-size-medium);
color: var(--env-var-color-2);
opacity: 0.6;
}
#modal-delete-account,
#modal-edit-org-name,
#modal-invite-member {
font-size: var(--env-var-font-size-large);
color: var(--env-var-color-2);
font-weight: 600;
}
.edit-profile-form__wrapper:not(:first-of-type),
.edit-password-form__wrapper:not(:first-of-type),
.edit-team-form__wrapper:not(:first-of-type) {
margin-top: 40px;
}
.edit-team-form__wrapper.compact {
margin-top: 30px;
color: var(--env-var-color-16);
}
.edit-profile-form__wrapper input,
.edit-password-form__wrapper input,
#edit-organization-name,
#input-team-member {
font-size: var(--env-var-font-size-medium);
padding: 10px;
}
.edit-password-form__wrapper > .announcement-tile {
width: 100%;
}
.edit-password-form__wrapper
> .announcement-tile
.announcement-content-subject {
margin-bottom: 2px;
}
.edit-password-form__wrapper .announcement-tile {
margin: 0;
}
.edit-password-form__wrapper .announcement-tile .announcement-close {
margin-left: auto;
}
.MuiBox-root > .announcement-tile {
border-color: var(--env-var-color-14);
background-color: var(--lighter-env-var-color-14);
}
.MuiBox-root > .announcement-tile .announcement-content-body,
.MuiBox-root > .announcement-tile .announcement-close svg {
color: var(--env-var-color-15);
fill: var(--env-var-color-15) !important;
}
#org-name-flex-container .Mui-disabled {
color: var(--env-var-color-2) !important;
}
.edit-team-form__wrapper td {
padding: 10px 0;
padding-top: 20px;
vertical-align: text-top;
}
.edit-team-form__wrapper th {
padding: 14px 0;
}
.edit-team-form__wrapper th:first-of-type {
padding-left: 20px;
}
.edit-team-form__wrapper td {
font-size: (--env-var-font-size-medium);
color: var(--env-var-color-16);
}
#select-actions,
#select-role {
padding: 5px 0;
padding-left: 15px;
padding-right: 50px;
line-height: 1.75;
}
.MuiInputBase-root:has(#select-actions) fieldset,
.MuiInputBase-root:has(#select-role) fieldset {
border-color: #a1a7b1;
margin: -1px;
border-width: 1px;
}
.MuiInputBase-root:has(#select-actions):hover fieldset,
.MuiInputBase-root:has(#select-role):hover fieldset {
border-color: var(--env-var-color-2);
}
.members-query{
display: inline-flex;
justify-content: center;
align-items: center;
margin-left: 6px;
padding: 2px;
font-size: 12px;
font-weight: 500;
color: var(--env-var-color-3);
background-color: #e0e9fd;
min-width: 22px;
min-height: 22px;
border-radius: 50%;
}
+78 -6
View File
@@ -1,9 +1,81 @@
import React from 'react' import PropTypes from "prop-types";
import { useState } from "react";
import {
Box,
Tab,
useTheme,
} from "@mui/material";
import TabContext from "@mui/lab/TabContext";
import TabList from "@mui/lab/TabList";
import "./index.css";
import ProfilePanel from "../../Components/TabPanels/ProfileSettings/ProfilePanel";
import PasswordPanel from "../../Components/TabPanels/ProfileSettings/PasswordPanel";
import TeamPanel from "../../Components/TabPanels/ProfileSettings/TeamPanel";
/**
* Settings component renders a settings page with tabs for Profile, Password, and Team settings.
*
* @returns {JSX.Element}
*/
const tabList = ["Profile", "Password", "Team"];
const Settings = () => { const Settings = () => {
return ( const theme = useTheme();
<div>Settings</div> //(tab) 0 - Profile
) //(tab) 1 - Password
} //(tab) 2 - Team
const [tab, setTab] = useState("0");
const handleTabChange = (event, newTab) => {
setTab(newTab);
};
export default Settings return (
<Box
//TODO - need to figure out document sizing
//TODO - breakpoints for responsive design
height="calc(100vh - 104px)"
minWidth={theme.spacing(55)}
width="100vw"
maxWidth="calc(100vw - 800px)"
mt={theme.spacing(8)}
pt={theme.spacing(5)}
px={theme.spacing(10)}
>
<TabContext value={tab}>
<Box width="100%" sx={{ borderBottom: 1, borderColor: "divider" }}>
<TabList onChange={handleTabChange} aria-label="settings tabs">
{tabList.map((label, index) => (
<Tab
label={label}
key={index}
value={index.toString()}
sx={{
fontSize: "13px",
color: theme.palette.secondary.main,
textTransform: "none",
minWidth: "fit-content",
paddingLeft: "0",
paddingY: "8px",
marginRight: "20px",
"&:focus": {
outline: "none",
},
}}
/>
))}
</TabList>
</Box>
<ProfilePanel />
<PasswordPanel />
<TeamPanel />
</TabContext>
</Box>
);
};
Settings.propTypes = {
// No props are being passed to this component, hence no specific PropTypes are defined.
};
export default Settings;
+2
View File
@@ -25,6 +25,7 @@ const otherColorsPurple = "#7f56d9";
const otherColorsWhite = "#fff"; const otherColorsWhite = "#fff";
const otherColorsGraishWhiteLight = "#f2f2f2"; const otherColorsGraishWhiteLight = "#f2f2f2";
const otherColorsStrongBlue = "#f2f2f2"; const otherColorsStrongBlue = "#f2f2f2";
const otherColorsSlateGray = "#667085";
// Global Font Family // Global Font Family
@@ -69,6 +70,7 @@ const theme = createTheme({
white: otherColorsWhite, white: otherColorsWhite,
graishWhiteLight: otherColorsGraishWhiteLight, graishWhiteLight: otherColorsGraishWhiteLight,
strongBlue: otherColorsStrongBlue, strongBlue: otherColorsStrongBlue,
slateGray: otherColorsSlateGray,
}, },
}, },
font :{ font :{
+6 -2
View File
@@ -3,9 +3,9 @@
line-height: 1.5; line-height: 1.5;
font-weight: 400; font-weight: 400;
color-scheme: light dark; /* color-scheme: light dark;
color: rgba(255, 255, 255, 0.87); color: rgba(255, 255, 255, 0.87);
background-color: #242424; background-color: #242424; */
font-synthesis: none; font-synthesis: none;
text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;
@@ -28,6 +28,10 @@
--env-var-color-11: #5d6b98; --env-var-color-11: #5d6b98;
--env-var-color-12: #182230; --env-var-color-12: #182230;
--env-var-color-13: #f9fafb; --env-var-color-13: #f9fafb;
--env-var-color-14: #fecf60;
--lighter-env-var-color-14: rgba(254, 207, 96, 0.1);
--env-var-color-15: #f79009;
--env-var-color-16: #667085;
--env-var-radius-1: 4px; --env-var-radius-1: 4px;
--env-var-radius-2: 8px; --env-var-radius-2: 8px;