added state containers, user model, and the button for user menu
This commit is contained in:
parent
4fd156f663
commit
f602a22b1b
7 changed files with 341 additions and 17 deletions
15
package-lock.json
generated
15
package-lock.json
generated
|
|
@ -16,6 +16,7 @@
|
||||||
"@mui/styles": "^6.1.6",
|
"@mui/styles": "^6.1.6",
|
||||||
"@types/classnames": "^2.3.0",
|
"@types/classnames": "^2.3.0",
|
||||||
"axios": "^1.7.7",
|
"axios": "^1.7.7",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#staging",
|
"protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#staging",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
|
|
@ -25,6 +26,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/types": "^7.25.8",
|
"@babel/types": "^7.25.8",
|
||||||
"@eslint/js": "^9.11.1",
|
"@eslint/js": "^9.11.1",
|
||||||
|
"@types/lodash": "^4.17.13",
|
||||||
"@types/react": "^18.3.10",
|
"@types/react": "^18.3.10",
|
||||||
"@types/react-dom": "^18.3.0",
|
"@types/react-dom": "^18.3.0",
|
||||||
"@vitejs/plugin-react": "^4.3.2",
|
"@vitejs/plugin-react": "^4.3.2",
|
||||||
|
|
@ -1857,6 +1859,13 @@
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/lodash": {
|
||||||
|
"version": "4.17.13",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.13.tgz",
|
||||||
|
"integrity": "sha512-lfx+dftrEZcdBPczf9d0Qv0x+j/rfNCMuC6OcfXmO8gkfeNAY88PgKUbvG56whcN23gc27yenwF6oJZXGFpYxg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/@types/long": {
|
"node_modules/@types/long": {
|
||||||
"version": "4.0.2",
|
"version": "4.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz",
|
||||||
|
|
@ -3626,6 +3635,12 @@
|
||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/lodash": {
|
||||||
|
"version": "4.17.21",
|
||||||
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||||
|
"integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/lodash.merge": {
|
"node_modules/lodash.merge": {
|
||||||
"version": "4.6.2",
|
"version": "4.6.2",
|
||||||
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@
|
||||||
"@mui/styles": "^6.1.6",
|
"@mui/styles": "^6.1.6",
|
||||||
"@types/classnames": "^2.3.0",
|
"@types/classnames": "^2.3.0",
|
||||||
"axios": "^1.7.7",
|
"axios": "^1.7.7",
|
||||||
|
"lodash": "^4.17.21",
|
||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#staging",
|
"protobuf-ts": "git+https://gitlab+deploy-token-50627:hv8mB4WkyvtjBpJKU1rN@gitlab.com/brandx/protobuf-ts.git#staging",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
|
|
@ -34,6 +35,7 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@babel/types": "^7.25.8",
|
"@babel/types": "^7.25.8",
|
||||||
"@eslint/js": "^9.11.1",
|
"@eslint/js": "^9.11.1",
|
||||||
|
"@types/lodash": "^4.17.13",
|
||||||
"@types/react": "^18.3.10",
|
"@types/react": "^18.3.10",
|
||||||
"@types/react-dom": "^18.3.0",
|
"@types/react-dom": "^18.3.0",
|
||||||
"@vitejs/plugin-react": "^4.3.2",
|
"@vitejs/plugin-react": "^4.3.2",
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import { makeStyles } from "@mui/styles";
|
||||||
import { getDarkLogo, getLightLogo, getSignatureAccentColour, hasTransparentLogoBG, hideLogo } from "../services/whiteLabel";
|
import { getDarkLogo, getLightLogo, getSignatureAccentColour, hasTransparentLogoBG, hideLogo } from "../services/whiteLabel";
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { useThemeType } from "../hooks/useThemeType";
|
import { useThemeType } from "../hooks/useThemeType";
|
||||||
|
import UserMenu from "../user/UserMenu";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme: Theme) => ({
|
const useStyles = makeStyles((theme: Theme) => ({
|
||||||
appBar: {
|
appBar: {
|
||||||
|
|
@ -53,10 +54,20 @@ const useStyles = makeStyles((theme: Theme) => ({
|
||||||
height: "64px"
|
height: "64px"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
appBarRight: {
|
||||||
|
display: "flex",
|
||||||
|
flexDirection: "row",
|
||||||
|
marginLeft: theme.spacing(2),
|
||||||
|
marginRight: theme.spacing(1),
|
||||||
|
[theme.breakpoints.up("md")]: {
|
||||||
|
marginLeft: theme.spacing(3),
|
||||||
|
marginRight: theme.spacing(2)
|
||||||
|
}
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
// toggleTheme: () => void;
|
toggleTheme: () => void;
|
||||||
sideIsOpen: boolean;
|
sideIsOpen: boolean;
|
||||||
openSide: () => void;
|
openSide: () => void;
|
||||||
// teams: Team[];
|
// teams: Team[];
|
||||||
|
|
@ -65,7 +76,7 @@ interface Props {
|
||||||
|
|
||||||
export default function Header(props: Props) {
|
export default function Header(props: Props) {
|
||||||
|
|
||||||
const { sideIsOpen, openSide} = props;
|
const { sideIsOpen, openSide, toggleTheme } = props;
|
||||||
|
|
||||||
const themeType = useThemeType();
|
const themeType = useThemeType();
|
||||||
const classes = useStyles()
|
const classes = useStyles()
|
||||||
|
|
@ -95,6 +106,9 @@ export default function Header(props: Props) {
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
|
<Box className={classes.appBarRight}>
|
||||||
|
<UserMenu toggleTheme={toggleTheme} />
|
||||||
|
</Box>
|
||||||
</Toolbar>
|
</Toolbar>
|
||||||
</AppBar>
|
</AppBar>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,15 @@ import { or } from '../utils/types'
|
||||||
import LoadingScreen from './LoadingScreen'
|
import LoadingScreen from './LoadingScreen'
|
||||||
import { pond } from "protobuf-ts/pond";
|
import { pond } from "protobuf-ts/pond";
|
||||||
import NavigationContainer from '../navigation/NavigationContainer'
|
import NavigationContainer from '../navigation/NavigationContainer'
|
||||||
|
import { GlobalState, GlobalStateAction, StateProvider } from '../providers/StateContainer'
|
||||||
|
import { User } from '../models/user'
|
||||||
|
|
||||||
|
const reducer = (state: GlobalState, action: GlobalStateAction): GlobalState => {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
[action.key]: action.value
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export default function UserWrapper() {
|
export default function UserWrapper() {
|
||||||
const [count, setCount] = useState(0)
|
const [count, setCount] = useState(0)
|
||||||
|
|
@ -18,6 +26,7 @@ export default function UserWrapper() {
|
||||||
const hasFetched = useRef(false);
|
const hasFetched = useRef(false);
|
||||||
const [user, setUser] = useState<pond.User | undefined>(undefined)
|
const [user, setUser] = useState<pond.User | undefined>(undefined)
|
||||||
const [message, setMessage] = useState("Loading user profile...")
|
const [message, setMessage] = useState("Loading user profile...")
|
||||||
|
const [global, setGlobal] = useState<undefined | GlobalState>(undefined)
|
||||||
|
|
||||||
let user_id = or(useAuth.user?.sub, "")
|
let user_id = or(useAuth.user?.sub, "")
|
||||||
|
|
||||||
|
|
@ -26,7 +35,15 @@ export default function UserWrapper() {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
userAPI.getUser(user_id).then(resp => {
|
userAPI.getUser(user_id).then(resp => {
|
||||||
setUser(resp)
|
setUser(resp)
|
||||||
|
setGlobal({
|
||||||
|
user: User.create(resp),
|
||||||
|
as: "",
|
||||||
|
showErrors: false,
|
||||||
|
userTeamPermissions: [],
|
||||||
|
backgroundTasksComplete: false
|
||||||
|
})
|
||||||
}).catch(err => {
|
}).catch(err => {
|
||||||
|
console.log(err.toString())
|
||||||
if (err.toString().includes("CORS")) {
|
if (err.toString().includes("CORS")) {
|
||||||
setMessage("CORS error")
|
setMessage("CORS error")
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -44,13 +61,14 @@ export default function UserWrapper() {
|
||||||
loadUser()
|
loadUser()
|
||||||
}, [loading])
|
}, [loading])
|
||||||
|
|
||||||
if (!user || loading) return (
|
if (!user || loading || !global) return (
|
||||||
<LoadingScreen
|
<LoadingScreen
|
||||||
message={message}
|
message={message}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<StateProvider state={global} reducer={reducer}>
|
||||||
<NavigationContainer>
|
<NavigationContainer>
|
||||||
<div>
|
<div>
|
||||||
<a href="https://vitejs.dev" target="_blank">
|
<a href="https://vitejs.dev" target="_blank">
|
||||||
|
|
@ -72,5 +90,7 @@ export default function UserWrapper() {
|
||||||
Coming soon
|
Coming soon
|
||||||
</p>
|
</p>
|
||||||
</NavigationContainer>
|
</NavigationContainer>
|
||||||
|
</StateProvider>
|
||||||
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
103
src/models/user.ts
Normal file
103
src/models/user.ts
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import { or } from "../utils/types";
|
||||||
|
import { cloneDeep } from "lodash";
|
||||||
|
|
||||||
|
const adminActions = [
|
||||||
|
"provision",
|
||||||
|
"upload-firmware",
|
||||||
|
"remove-devices",
|
||||||
|
"copy-token",
|
||||||
|
"recluse",
|
||||||
|
"pause-data"
|
||||||
|
];
|
||||||
|
|
||||||
|
const adminFeatures = [
|
||||||
|
"beta",
|
||||||
|
"billing",
|
||||||
|
"security",
|
||||||
|
"dev-channel",
|
||||||
|
"docs",
|
||||||
|
"maps",
|
||||||
|
"sleep",
|
||||||
|
"support",
|
||||||
|
"json",
|
||||||
|
"tasks",
|
||||||
|
"teams",
|
||||||
|
"developer"
|
||||||
|
];
|
||||||
|
|
||||||
|
export class User {
|
||||||
|
public settings: pond.UserSettings = pond.UserSettings.create();
|
||||||
|
public status: pond.UserStatus = pond.UserStatus.create();
|
||||||
|
public permissions: pond.Permission[] = [];
|
||||||
|
public preferences: pond.UserPreferences = pond.UserPreferences.create();
|
||||||
|
|
||||||
|
public static create(pb?: pond.User): User {
|
||||||
|
let my = new User();
|
||||||
|
if (pb) {
|
||||||
|
my.settings = pond.UserSettings.fromObject(cloneDeep(or(pb.settings, {})));
|
||||||
|
my.status = pond.UserStatus.fromObject(cloneDeep(or(pb.status, {})));
|
||||||
|
my.permissions = cloneDeep(pb.permissions);
|
||||||
|
my.preferences = pond.UserPreferences.fromObject(cloneDeep(or(pb.preferences, {})));
|
||||||
|
}
|
||||||
|
return my;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static any(data: any): User {
|
||||||
|
return User.create(pond.User.fromObject(cloneDeep(data)));
|
||||||
|
}
|
||||||
|
|
||||||
|
public empty(): boolean {
|
||||||
|
return this.id() === "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public id(): string {
|
||||||
|
return this.settings.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public name(): string {
|
||||||
|
return this.settings.name !== "" ? this.settings.name : "User " + this.settings.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static clone(other?: User): User {
|
||||||
|
if (other) {
|
||||||
|
return User.create(
|
||||||
|
pond.User.fromObject({
|
||||||
|
settings: pond.UserSettings.fromObject(cloneDeep(or(other.settings, {}))),
|
||||||
|
status: pond.UserStatus.fromObject(cloneDeep(or(other.status, {}))),
|
||||||
|
permissions: cloneDeep(other.permissions),
|
||||||
|
preferences: pond.UserPreferences.fromObject(cloneDeep(or(other.preferences, {})))
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return User.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
public protobuf(): pond.User {
|
||||||
|
return pond.User.fromObject({
|
||||||
|
settings: this.settings,
|
||||||
|
status: this.status
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public hasAdmin(): boolean {
|
||||||
|
if (this.settings.features.includes("admin")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public allowedTo(action: string): boolean {
|
||||||
|
if (this.hasAdmin() && adminActions.includes(action)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return this.settings.actions.includes(action);
|
||||||
|
}
|
||||||
|
|
||||||
|
public hasFeature(flag: string): boolean {
|
||||||
|
if (this.hasAdmin() && adminFeatures.includes(flag)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return this.settings.features.includes(flag);
|
||||||
|
}
|
||||||
|
}
|
||||||
66
src/providers/StateContainer.tsx
Normal file
66
src/providers/StateContainer.tsx
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
import { createContext, useContext, useReducer, Dispatch } from "react";
|
||||||
|
// import { User } from "../models";
|
||||||
|
import { pond } from "protobuf-ts/pond";
|
||||||
|
import { User } from "../models/user";
|
||||||
|
|
||||||
|
export interface GlobalState {
|
||||||
|
// tags: Tag[];
|
||||||
|
user: User;
|
||||||
|
// team: Team;
|
||||||
|
as: string;
|
||||||
|
// firmware: Map<string, Firmware>;
|
||||||
|
// newStructure: boolean;
|
||||||
|
showErrors: boolean;
|
||||||
|
userTeamPermissions: pond.Permission[];
|
||||||
|
backgroundTasksComplete: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GlobalStateAction {
|
||||||
|
key: keyof GlobalState;
|
||||||
|
value: GlobalState[keyof GlobalState];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const StateContext = createContext<[GlobalState, Dispatch<GlobalStateAction>]>([
|
||||||
|
{} as GlobalState,
|
||||||
|
{} as Dispatch<GlobalStateAction>
|
||||||
|
]);
|
||||||
|
|
||||||
|
interface StateProviderProps {
|
||||||
|
state: GlobalState;
|
||||||
|
reducer: (ctx: GlobalState, action: GlobalStateAction) => GlobalState;
|
||||||
|
// user: User;
|
||||||
|
children?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const StateProvider = (props: StateProviderProps) => {
|
||||||
|
return (
|
||||||
|
<StateContext.Provider value={useReducer(props.reducer, props.state)}>
|
||||||
|
{props.children}
|
||||||
|
</StateContext.Provider>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
//-------------State container usage-------------
|
||||||
|
|
||||||
|
// a) Functional Components (hook)
|
||||||
|
export const useGlobalState = () => useContext(StateContext);
|
||||||
|
/*
|
||||||
|
1. Import the hook:
|
||||||
|
import { useGlobalState } from "providers";
|
||||||
|
|
||||||
|
2. Access the context members by binding it (x is an example context member);
|
||||||
|
const [{x}, dispatch] = useGlobalState();
|
||||||
|
*/
|
||||||
|
|
||||||
|
// b) Class Components (context)
|
||||||
|
/*
|
||||||
|
1. Import state context:
|
||||||
|
import { StateContext } from "providers";
|
||||||
|
|
||||||
|
2. Add the context to the beginning of the class component:
|
||||||
|
static contextType = StateContext;
|
||||||
|
context!: React.ContextType<typeof StateContext>;
|
||||||
|
|
||||||
|
3. Access the context where x is a key of context:
|
||||||
|
const [{x}, dispatch] = this.context;
|
||||||
|
*/
|
||||||
104
src/user/UserMenu.tsx
Normal file
104
src/user/UserMenu.tsx
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
import { Avatar, Button, PaletteColor, Theme, Typography, useTheme } from "@mui/material";
|
||||||
|
import { useGlobalState } from "../providers/StateContainer";
|
||||||
|
import { getSecondaryColour, getSignatureAccentColour } from "../services/whiteLabel";
|
||||||
|
import { makeStyles } from "@mui/styles";
|
||||||
|
import { Person } from "@mui/icons-material";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
|
const useStyles = makeStyles((theme: Theme) => ({
|
||||||
|
userAvatar: {
|
||||||
|
color: "#fff",
|
||||||
|
backgroundColor: getSecondaryColour()["700" as keyof PaletteColor],
|
||||||
|
[theme.breakpoints.down("xs")]: {
|
||||||
|
width: "32px",
|
||||||
|
height: "32px"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
profileButton: {
|
||||||
|
maxWidth: "50vw"
|
||||||
|
},
|
||||||
|
profileName: {
|
||||||
|
whiteSpace: "nowrap",
|
||||||
|
overflow: "hidden",
|
||||||
|
textOverflow: "ellipsis",
|
||||||
|
textTransform: "capitalize",
|
||||||
|
color: getSignatureAccentColour(),
|
||||||
|
marginBottom: -3,
|
||||||
|
marginTop: -3
|
||||||
|
},
|
||||||
|
red: {
|
||||||
|
color: "var(--status-alert)"
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
toggleTheme: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function UserMenu(props: Props) {
|
||||||
|
|
||||||
|
const { toggleTheme } = props;
|
||||||
|
const [{ user }] = useGlobalState();
|
||||||
|
const classes = useStyles();
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
const name = user.name();
|
||||||
|
const picture = user.settings.avatar;
|
||||||
|
const hasAdmin = user.hasFeature ? user.hasFeature("admin") : false;
|
||||||
|
const allowedToCopyToken = user.allowedTo("copy-token");
|
||||||
|
const hasTeams = user.hasFeature ? user.hasFeature("teams") : false;
|
||||||
|
// const hasBilling = user.settings.useTeam
|
||||||
|
// ? userTeamPermissions.includes(pond.Permission.PERMISSION_BILLING)
|
||||||
|
// : true;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
console.log(user)
|
||||||
|
}, [user])
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
id="tour-user-menu"
|
||||||
|
// aria-owns={userMenuIsOpen ? "userMenu" : undefined}
|
||||||
|
aria-haspopup="true"
|
||||||
|
// onClick={openUserMenu}
|
||||||
|
className={classes.profileButton}>
|
||||||
|
<div style={{ display: "flex", flexDirection: "column" }}>
|
||||||
|
<Typography className={classes.profileName} variant="button">
|
||||||
|
{name}
|
||||||
|
</Typography>
|
||||||
|
{/* {team.settings.name && hasTeams && (
|
||||||
|
<Typography className={classes.profileName} variant="button">
|
||||||
|
({team.settings.name})
|
||||||
|
</Typography>
|
||||||
|
)} */}
|
||||||
|
</div>
|
||||||
|
<div style={{ marginLeft: theme.spacing(1) }}>
|
||||||
|
{/* {picture && hasTeams ? (
|
||||||
|
<div>
|
||||||
|
<UserAvatar
|
||||||
|
user={user}
|
||||||
|
className={classes.userAvatar2}
|
||||||
|
style={{ zIndex: as === "" ? 1000 : 0 }}
|
||||||
|
/>
|
||||||
|
<div style={{ position: "relative" }}>
|
||||||
|
<Avatar
|
||||||
|
src={team.settings.avatar}
|
||||||
|
className={classes.teamAvatar}
|
||||||
|
style={{ background: purple[500] }}>
|
||||||
|
<TeamIcon style={{ fontSize: 38 }} />
|
||||||
|
</Avatar>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : picture ? (
|
||||||
|
<Avatar alt={name} src={picture} className={classes.userAvatar}>
|
||||||
|
{!picture && name}
|
||||||
|
</Avatar>
|
||||||
|
) : ( */}
|
||||||
|
<Avatar alt={name} src={picture} className={classes.userAvatar}>
|
||||||
|
{/* <Person /> */}
|
||||||
|
</Avatar>
|
||||||
|
{/* )} */}
|
||||||
|
</div>
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue