add user management UI

This commit is contained in:
Sidney Alcantara
2021-09-03 16:44:55 +10:00
parent b134e9c5cd
commit ada9db36ac
10 changed files with 300 additions and 14 deletions

View File

@@ -55,6 +55,7 @@
"react-router-dom": "^5.0.1",
"react-scripts": "^4.0.3",
"react-scroll-sync": "^0.8.0",
"react-use-search": "^0.3.1",
"react-usestateref": "^1.0.5",
"serve": "^11.3.2",
"tinymce": "^5.2.0",

View File

@@ -0,0 +1,9 @@
import SvgIcon, { SvgIconProps } from "@material-ui/core/SvgIcon";
export default function Copy(props: SvgIconProps) {
return (
<SvgIcon {...props}>
<path d="M18 7a2 2 0 012 2v10a2 2 0 01-2 2h-8a2 2 0 01-2-2V9a2 2 0 012-2h8zm0 2h-8v10h8V9zM4 15V5a2 2 0 012-2h8a2 2 0 012 2H6v12a2 2 0 01-2-2z" />
</SvgIcon>
);
}

View File

@@ -0,0 +1,66 @@
import {
useScrollTrigger,
Paper,
TextField,
FilledTextFieldProps,
InputAdornment,
} from "@material-ui/core";
import SearchIcon from "@material-ui/icons/Search";
export interface IFloatingSearchProps extends Partial<FilledTextFieldProps> {
label: string;
}
export default function FloatingSearch({
label,
...props
}: IFloatingSearchProps) {
const trigger = useScrollTrigger({ disableHysteresis: true, threshold: 0 });
return (
<Paper
elevation={trigger ? 8 : 1}
sx={{
position: "sticky",
top: (theme) => theme.spacing(7 + 1),
zIndex: "appBar",
}}
>
<TextField
label={label}
placeholder={label}
hiddenLabel
fullWidth
type="search"
id="user-management-search"
size="medium"
InputProps={{
startAdornment: (
<InputAdornment
position="start"
sx={{ px: 0.5, pointerEvents: "none" }}
>
<SearchIcon />
</InputAdornment>
),
}}
sx={{
"& .MuiInputLabel-root": {
opacity: 0,
mt: -5,
mb: 2,
},
"& .MuiFilledInput-root": {
boxShadow: 0,
borderRadius: 2,
"&::before": {
borderRadius: 2,
height: (theme) => (theme.shape.borderRadius as number) * 4,
},
},
}}
{...props}
/>
</Paper>
);
}

View File

@@ -0,0 +1,104 @@
import {
ListItem,
ListItemAvatar,
Avatar,
ListItemText,
Tooltip,
IconButton,
} from "@material-ui/core";
import CopyIcon from "assets/icons/Copy";
import DeleteIcon from "@material-ui/icons/DeleteOutlined";
import MultiSelect from "@antlerengineering/multiselect";
import { User } from "pages/Settings/UserManagement";
export default function UserItem({
id,
user: { displayName, email, photoURL },
}: User) {
return (
<ListItem
children={
<>
<ListItemAvatar>
<Avatar src={photoURL}>SM</Avatar>
</ListItemAvatar>
<ListItemText
primary={displayName}
secondary={email}
primaryTypographyProps={{
style: { userSelect: "all" },
}}
secondaryTypographyProps={{
noWrap: true,
style: { userSelect: "all" },
}}
/>
</>
}
secondaryAction={
<>
<MultiSelect
label="Roles"
value={["ADMIN"]}
options={["ADMIN"]}
onChange={console.log}
TextFieldProps={{
fullWidth: false,
sx: {
mr: 0.5,
"& .MuiInputLabel-root": {
opacity: 0,
mt: -3,
},
"& .MuiFilledInput-root": {
boxShadow: 0,
"&::before": { content: "none" },
"&:hover, &.Mui-focused": { bgcolor: "action.hover" },
},
"& .MuiSelect-select.MuiFilledInput-input": {
typography: "button",
pl: 1,
pr: 3.5,
},
"& .MuiSelect-icon": {
right: 2,
},
},
}}
/>
<Tooltip title="Copy UID">
<IconButton
aria-label="Copy UID"
onClick={() => navigator.clipboard.writeText(id)}
>
<CopyIcon />
</IconButton>
</Tooltip>
<Tooltip title="Delete">
<IconButton aria-label="Delete" color="error">
<DeleteIcon />
</IconButton>
</Tooltip>
</>
}
sx={{
pr: 1,
"& .MuiListItemSecondaryAction-root": {
position: "static",
transform: "none",
marginLeft: "auto",
display: "flex",
alignItems: "center",
},
}}
/>
);
}

View File

@@ -0,0 +1,40 @@
import {
Skeleton,
ListItem,
ListItemAvatar,
Avatar,
ListItemText,
Stack,
} from "@material-ui/core";
export default function UserSkeleton() {
return (
<ListItem
children={
<>
<ListItemAvatar>
<Skeleton variant="circular">
<Avatar />
</Skeleton>
</ListItemAvatar>
<ListItemText
primary={<Skeleton width={80} />}
secondary={<Skeleton width={120} />}
/>
</>
}
secondaryAction={
<Stack
spacing={2}
alignItems="center"
direction="row"
sx={{ pr: 1.25 }}
>
<Skeleton width={80} height={32} />
<Skeleton variant="circular" width={24} height={24} />
<Skeleton variant="circular" width={24} height={24} />
</Stack>
}
/>
);
}

View File

@@ -4,9 +4,9 @@ import { FormatterProps } from "react-data-grid";
import { makeStyles, createStyles } from "@material-ui/styles";
import { Grid, Tooltip, IconButton } from "@material-ui/core";
import CopyCellsIcon from "assets/icons/CopyCells";
import DeleteIcon from "@material-ui/icons/DeleteForever";
import DeleteIcon from "@material-ui/icons/DeleteOutlined";
import { SnackContext } from "contexts/SnackContext";
// import { SnackContext } from "contexts/SnackContext";
import { useConfirmation } from "components/ConfirmationDialog/Context";
import { useRowyContext } from "contexts/RowyContext";
import useKeyPress from "hooks/useKeyPress";
@@ -33,13 +33,13 @@ export default function FinalColumn({ row }: FormatterProps<any, any>) {
const { requestConfirmation } = useConfirmation();
const { tableActions } = useRowyContext();
const shiftPress = useKeyPress("Shift");
const snack = useContext(SnackContext);
// const snack = useContext(SnackContext);
const handleDelete = async () => tableActions?.row.delete(row.id);
return (
<Grid container spacing={1}>
<Grid item>
<Tooltip title="Duplicate row">
<Tooltip title="Duplicate Row">
<IconButton
size="small"
color="inherit"
@@ -58,7 +58,7 @@ export default function FinalColumn({ row }: FormatterProps<any, any>) {
});
if (tableActions) tableActions?.row.add(clonedRow);
}}
aria-label="Duplicate row"
aria-label="Duplicate Row"
>
<CopyCellsIcon />
</IconButton>
@@ -66,13 +66,13 @@ export default function FinalColumn({ row }: FormatterProps<any, any>) {
</Grid>
<Grid item>
<Tooltip title="Delete row">
<Tooltip title="Delete Row">
{shiftPress ? (
<IconButton
size="small"
color="inherit"
onClick={handleDelete}
aria-label="Delete row"
aria-label="Delete Row"
>
<DeleteIcon />
</IconButton>

View File

@@ -21,7 +21,7 @@ export interface IProjectSettingsChildProps {
updatePublicSettings: (data: Record<string, any>) => void;
}
export default function ProjectSettings() {
export default function ProjectSettingsPage() {
const snack = useSnackContext();
const [settingsState] = useDoc({ path: SETTINGS }, { createIfMissing: true });

View File

@@ -1,13 +1,72 @@
import { Container, Stack } from "@material-ui/core";
import { useSearch } from "react-use-search";
import SettingsSection from "components/Settings/SettingsSection";
import { Container, Stack, Typography, Paper, List } from "@material-ui/core";
import FloatingSearch from "components/FloatingSearch";
import UserItem from "components/Settings/UserManagement/UserItem";
import UserSkeleton from "components/Settings/UserManagement/UserSkeleton";
import useCollection from "hooks/useCollection";
import { USERS } from "config/dbPaths";
export interface User {
id: string;
user: {
displayName: string;
email: string;
photoURL: string;
};
}
export default function UserManagementPage() {
const [usersState] = useCollection({ path: USERS });
const loading = usersState.loading || !Array.isArray(usersState.documents);
const [filteredUsers, query, handleChange] = useSearch<User>(
usersState.documents ?? [],
(user, query) =>
user.id === query ||
user.user.displayName.includes(query) ||
user.user.email.includes(query),
{ filter: true, debounce: 200 }
);
export default function UserManagement() {
return (
<Container maxWidth="sm" sx={{ px: 1, pt: 2, pb: 7 }}>
<Stack spacing={4}>
<SettingsSection title="Users">TODO:</SettingsSection>
<FloatingSearch label="Search Users" onChange={handleChange as any} />
<Stack
direction="row"
spacing={2}
justifyContent="space-between"
alignItems="baseline"
sx={{ mt: 4, mx: 1, mb: 0.5, cursor: "default" }}
>
<Typography variant="subtitle1" component="h2">
Users
</Typography>
{!loading && (
<Typography variant="button" component="div">
{query
? `${filteredUsers.length} of ${usersState.documents.length}`
: usersState.documents.length}
</Typography>
)}
</Stack>
<Paper>
<List>
{loading || (query === "" && filteredUsers.length === 0) ? (
<>
<UserSkeleton />
<UserSkeleton />
<UserSkeleton />
</>
) : (
filteredUsers.map((user) => <UserItem key={user.id} {...user} />)
)}
</List>
</Paper>
</Container>
);
}

View File

@@ -2,7 +2,7 @@ import { Container, Stack } from "@material-ui/core";
import SettingsSection from "components/Settings/SettingsSection";
export default function UserSettings() {
export default function UserSettingsPage() {
return (
<Container maxWidth="sm" sx={{ px: 1, pt: 2, pb: 7 }}>
<Stack spacing={4}>

View File

@@ -13740,6 +13740,13 @@ react-transition-group@^4.4.0, react-transition-group@^4.4.1:
loose-envify "^1.4.0"
prop-types "^15.6.2"
react-use-search@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/react-use-search/-/react-use-search-0.3.1.tgz#1a36434a0f0a0fc2600a743a2c170ad88f718787"
integrity sha512-Ec8+5AnOMunnA532ziNLzs3W/4l9MhecvRF1fi2ho9JvXBgT/pmdFpgN3fRfPCHAsMf9LpEo4LxDZDs/BdTfNQ==
dependencies:
lodash.debounce "^4.0.8"
react-usestateref@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/react-usestateref/-/react-usestateref-1.0.5.tgz#0b5659217022a7e88087875ec1745730940b7882"