diff --git a/net/web/src/constants/Colors.js b/net/web/src/constants/Colors.js
index a5e3ddf4..b8093f7d 100644
--- a/net/web/src/constants/Colors.js
+++ b/net/web/src/constants/Colors.js
@@ -2,8 +2,12 @@ const Colors = {
background: '#8fbea7',
primary: '#8fbea7',
formBackground: '#f4f4f4',
+ formHover: '#e8e8e8',
grey: '#888888',
white: '#f8f8f8',
+ divider: '#dddddd',
+ encircle: '#cccccc',
+ alert: '#ff8888',
};
export default Colors;
diff --git a/net/web/src/context/useViewportContext.hook.js b/net/web/src/context/useViewportContext.hook.js
index 2b9bf7a6..b64ba9b8 100644
--- a/net/web/src/context/useViewportContext.hook.js
+++ b/net/web/src/context/useViewportContext.hook.js
@@ -3,9 +3,9 @@ import { useEffect, useState } from 'react';
export function useViewportContext() {
const [state, setState] = useState({ });
- const SMALL_MEDIUM = 640;
- const MEDIUM_LARGE = 1024;
- const LARGE_XLARGE = 1600;
+ const SMALL_MEDIUM = 600;
+ const MEDIUM_LARGE = 1000;
+ const LARGE_XLARGE = 1400;
const updateState = (value) => {
setState((s) => ({ ...s, ...value }));
diff --git a/net/web/src/images/avatar.png b/net/web/src/images/avatar.png
new file mode 100644
index 00000000..638b6543
Binary files /dev/null and b/net/web/src/images/avatar.png differ
diff --git a/net/web/src/images/login.png b/net/web/src/images/login.png
index deed8094..4cbace0a 100644
Binary files a/net/web/src/images/login.png and b/net/web/src/images/login.png differ
diff --git a/net/web/src/logo/Logo.jsx b/net/web/src/logo/Logo.jsx
new file mode 100644
index 00000000..5d87a801
--- /dev/null
+++ b/net/web/src/logo/Logo.jsx
@@ -0,0 +1,14 @@
+import avatar from 'images/avatar.png';
+
+export function Logo({ url, width, height, radius }) {
+ return (
+
+ { url && (
+
![logo]({url})
+ )}
+ { !url && (
+
![default logo]({avatar})
+ )}
+
+ );
+}
diff --git a/net/web/src/session/Session.styled.js b/net/web/src/session/Session.styled.js
index 2f339dbd..9a67d841 100644
--- a/net/web/src/session/Session.styled.js
+++ b/net/web/src/session/Session.styled.js
@@ -11,11 +11,31 @@ export const SessionWrapper = styled.div`
}
.desktop-layout {
+ width: 100%;
+ height: 100%;
+ display: flex;
+ flex-direction: row;
+
.left {
+ min-width: 256px;
+ max-width: 384px;
+ width: 20%;
+ height: 100%;
+ background-color: yellow;
+ display: flex;
+ flex-direction: column;
}
.center {
+ flex-grow: 1;
}
.right {
+ min-width: 256px;
+ max-width: 384px;
+ width: 20%;
+ height: 100%;
+ background-color: yellow;
+ display: flex;
+ flex-direction: column;
}
}
diff --git a/net/web/src/session/identity/Identity.jsx b/net/web/src/session/identity/Identity.jsx
index e157f8cf..b49befe6 100644
--- a/net/web/src/session/identity/Identity.jsx
+++ b/net/web/src/session/identity/Identity.jsx
@@ -1,11 +1,54 @@
import { useContext } from 'react';
import { AppContext } from 'context/AppContext';
-import { Button } from 'antd';
+import { Dropdown, Menu, Tooltip } from 'antd';
+import { Logo } from 'logo/Logo';
+import { IdentityWrapper } from './Identity.styled';
+import { useIdentity } from './useIdentity.hook';
+import { ExclamationCircleOutlined, DownOutlined } from '@ant-design/icons';
export function Identity() {
- const app = useContext(AppContext);
+ const { state, actions } = useIdentity();
- return
+ const menu = (
+
+ );
+
+ return (
+
+
+ { state.init && (
+
+ )}
+
+
{state.name}
+
+
+ { state.disconnected && (
+
+
+
+ )}
+
+
{state.handle}
+
+
+
+
+
+
+
+
+ );
}
diff --git a/net/web/src/session/identity/Identity.styled.js b/net/web/src/session/identity/Identity.styled.js
new file mode 100644
index 00000000..c7b83488
--- /dev/null
+++ b/net/web/src/session/identity/Identity.styled.js
@@ -0,0 +1,56 @@
+import styled from 'styled-components';
+import Colors from 'constants/Colors';
+
+export const IdentityWrapper = styled.div`
+ width: 100%;
+ height: 64px;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ padding-left: 16px;
+ padding-right: 16px;
+ border-bottom: 1px solid ${Colors.divider};
+ background-color: ${Colors.formBackground};
+
+ &:hover {
+ cursor: pointer;
+
+ .drop {
+ border: 1px solid ${Colors.encircle};
+ background-color: ${Colors.formHover};
+ }
+ }
+
+ .drop {
+ padding-left: 4px;
+ padding-right: 4px;
+ border-radius: 8px;
+ border: 1px solid ${Colors.formBackground};
+ }
+
+ .label {
+ flex-grow: 1;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+
+ .name {
+ font-size: 1.2em;
+ }
+
+ .handle {
+ display: flex;
+ flex-direction: row;
+ justify-content: center;
+ align-items: center;
+ font-weight: bold;
+
+ .alert {
+ width: 24px;
+ color: ${Colors.alert};
+ }
+ }
+ }
+`;
+
diff --git a/net/web/src/session/identity/useIdentity.hook.js b/net/web/src/session/identity/useIdentity.hook.js
new file mode 100644
index 00000000..4a737298
--- /dev/null
+++ b/net/web/src/session/identity/useIdentity.hook.js
@@ -0,0 +1,42 @@
+import { useState, useEffect, useContext } from 'react';
+import { ProfileContext } from 'context/ProfileContext';
+import { AppContext } from 'context/AppContext';
+
+export function useIdentity() {
+
+ const [state, setState] = useState({
+ url: null,
+ name: null,
+ handle: null,
+ disconnected: false,
+ init: false,
+ });
+
+ const app = useContext(AppContext);
+ const profile = useContext(ProfileContext);
+
+ const updateState = (value) => {
+ setState((s) => ({ ...s, ...value }));
+ }
+
+ useEffect(() => {
+ if (profile.state.init) {
+ const { name, handle, image } = profile.state.profile;
+ let url = !image ? null : profile.actions.profileImageUrl();
+ updateState({ init: true, name, handle, url });
+ }
+ }, [profile]);
+
+ useEffect(() => {
+ if (app.state) {
+ updateState({ disconnected: app.state.disconnected });
+ }
+ }, [app]);
+
+ const actions = {
+ logout: app.actions.logout,
+ };
+
+ return { state, actions };
+}
+