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 && ( + default logo + )} +
+ ); +} 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 = ( + + +
Edit Profile
+
+ +
Change Login
+
+ +
Logout
+
+
+ ); + + 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 }; +} +