+
+
+
+ Vite + React
+
@@ -71,6 +89,8 @@ export default function UserWrapper() {
Coming soon
-
+
+
+
)
}
\ No newline at end of file
diff --git a/src/models/user.ts b/src/models/user.ts
new file mode 100644
index 0000000..842a077
--- /dev/null
+++ b/src/models/user.ts
@@ -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);
+ }
+}
diff --git a/src/providers/StateContainer.tsx b/src/providers/StateContainer.tsx
new file mode 100644
index 0000000..1558d9d
--- /dev/null
+++ b/src/providers/StateContainer.tsx
@@ -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
;
+// 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]>([
+ {} as GlobalState,
+ {} as Dispatch
+]);
+
+interface StateProviderProps {
+ state: GlobalState;
+ reducer: (ctx: GlobalState, action: GlobalStateAction) => GlobalState;
+// user: User;
+ children?: any;
+}
+
+export const StateProvider = (props: StateProviderProps) => {
+ return (
+
+ {props.children}
+
+ );
+};
+
+//-------------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;
+
+ 3. Access the context where x is a key of context:
+ const [{x}, dispatch] = this.context;
+*/
diff --git a/src/user/UserMenu.tsx b/src/user/UserMenu.tsx
new file mode 100644
index 0000000..8bfdbdf
--- /dev/null
+++ b/src/user/UserMenu.tsx
@@ -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 (
+
+ )
+}
\ No newline at end of file