diff --git a/net/server/internal/api_getProfileImage.go b/net/server/internal/api_getProfileImage.go index 41c80fad..79d8e504 100644 --- a/net/server/internal/api_getProfileImage.go +++ b/net/server/internal/api_getProfileImage.go @@ -11,7 +11,7 @@ import ( func GetProfileImage(w http.ResponseWriter, r *http.Request) { var data []byte - account, code, err := BearerAppToken(r, true); + account, code, err := ParamAppToken(r, true); if err != nil { ErrResponse(w, code, err) return diff --git a/net/server/internal/authUtil.go b/net/server/internal/authUtil.go index 2f5e564c..60196d30 100644 --- a/net/server/internal/authUtil.go +++ b/net/server/internal/authUtil.go @@ -77,6 +77,40 @@ func BearerAccountToken(r *http.Request) (*store.AccountToken, error) { return &accountToken, nil } +func ParamAppToken(r *http.Request, detail bool) (*store.Account, int, error) { + + // parse authentication token + target, access, err := ParseToken(r.FormValue("token")) + if err != nil { + return nil, http.StatusBadRequest, err + } + + // find token record + var app store.App + if detail { + if err := store.DB.Preload("Account.AccountDetail").Where("account_id = ? AND token = ?", target, access).First(&app).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, http.StatusNotFound, err + } else { + return nil, http.StatusInternalServerError, err + } + } + } else { + if err := store.DB.Preload("Account").Where("account_id = ? AND token = ?", target, access).First(&app).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil, http.StatusNotFound, err + } else { + return nil, http.StatusInternalServerError, err + } + } + } + if app.Account.Disabled { + return nil, http.StatusGone, errors.New("account is inactive") + } + + return &app.Account, http.StatusOK, nil +} + func BearerAppToken(r *http.Request, detail bool) (*store.Account, int, error) { // parse bearer authentication diff --git a/net/server/internal/modelUtil.go b/net/server/internal/modelUtil.go index 850f32dd..7638bb6c 100644 --- a/net/server/internal/modelUtil.go +++ b/net/server/internal/modelUtil.go @@ -9,6 +9,7 @@ func getProfileModel(account *store.Account) *Profile { return &Profile{ Guid: account.Guid, Handle: account.Username, + Name: account.AccountDetail.Name, Description: account.AccountDetail.Description, Location: account.AccountDetail.Location, Image: account.AccountDetail.Image, diff --git a/net/server/internal/ucProfileUpdate_test.go b/net/server/internal/ucProfileUpdate_test.go index 94c293eb..17dc02c4 100644 --- a/net/server/internal/ucProfileUpdate_test.go +++ b/net/server/internal/ucProfileUpdate_test.go @@ -102,7 +102,7 @@ func TestProfileUpdate(t *testing.T) { APP_TOKENAPP, set.A.Token, &profile, nil)) // retrieve profile image - data, hdr, err = ApiTestData(GetProfileImage, "GET", "/profile/image", nil, nil, + data, hdr, err = ApiTestData(GetProfileImage, "GET", "/profile/image?token=" + set.A.Token, nil, nil, APP_TOKENAPP, set.A.Token, 0, 0) assert.NoError(t, err) diff --git a/net/web/src/Login/Login.jsx b/net/web/src/Login/Login.jsx index c0c573c8..4aa50391 100644 --- a/net/web/src/Login/Login.jsx +++ b/net/web/src/Login/Login.jsx @@ -7,6 +7,12 @@ export function Login(props) { const { state, actions } = useLogin() + const keyDown = (e) => { + if (e.key === 'Enter') { + actions.onLogin() + } + } + return(
@@ -15,9 +21,9 @@ export function Login(props) { Communication for the Decentralized Web
} - onChange={(e) => actions.setUsername(e.target.value)} value={state.username} /> + onChange={(e) => actions.setUsername(e.target.value)} value={state.username} onKeyDown={(e) => keyDown(e)}/> } - onChange={(e) => actions.setPassword(e.target.value)} value={state.password} /> + onChange={(e) => actions.setPassword(e.target.value)} value={state.password} onKeyDown={(e) => keyDown(e)}/> actions.onLogin()} disabled={actions.isDisabled()}> Sign In diff --git a/net/web/src/Login/useLogin.hook.js b/net/web/src/Login/useLogin.hook.js index 9c5ee889..84f50648 100644 --- a/net/web/src/Login/useLogin.hook.js +++ b/net/web/src/Login/useLogin.hook.js @@ -28,7 +28,7 @@ export function useLogin() { return false }, onLogin: async () => { - if (!state.spinning) { + if (!state.spinning && state.username != '' && state.password != '') { actions.updateState({ spinning: true }) try { await app.actions.login(state.username, state.password) diff --git a/net/web/src/User/SideBar/Identity/Identity.jsx b/net/web/src/User/SideBar/Identity/Identity.jsx new file mode 100644 index 00000000..a93e4d58 --- /dev/null +++ b/net/web/src/User/SideBar/Identity/Identity.jsx @@ -0,0 +1,48 @@ +import { Avatar, Image } from 'antd'; +import React from 'react' +import { IdentityWrapper } from './Identity.styled'; +import { DownOutlined, EditOutlined, UserOutlined } from '@ant-design/icons'; +import { useIdentity } from './useIdentity.hook'; +import { Menu, Dropdown } from 'antd'; + +export function Identity() { + + const { state, actions } = useIdentity() + + const Logo = () => { + if (state.imageUrl === '') { + return } /> + } + return } /> + } + + const menu = ( + + +
{}}>Edit Profile
+
+ +
actions.logout()}>Sign Out
+
+
+ ); + + return ( + + +
+
+ +
+ { state.name } + { state.handle } +
+ +
+
+
+
+ ) +} diff --git a/net/web/src/User/SideBar/Identity/Identity.styled.js b/net/web/src/User/SideBar/Identity/Identity.styled.js new file mode 100644 index 00000000..0fbdde75 --- /dev/null +++ b/net/web/src/User/SideBar/Identity/Identity.styled.js @@ -0,0 +1,68 @@ +import { Button } from 'antd'; +import styled from 'styled-components'; + +export const IdentityWrapper = styled.div` + background-color: #f6f5ed; + border-bottom: 2px solid #8fbea7; + border-top: 1px solid #8fbea7; + border-left: 0px; + border-right: 0px; + border-radius: 0px; + &:hover { + cursor: pointer; + } + + .container { + display: flex; + flex-direction: row; + align-items: center; + justify-content: center; + padding-left: 16px; + padding-right: 16px; + } + + .menu { + min-width: 0px; + } + + .logo { + width: 33% + display: flex; + align-items: center; + justify-content: center; + padding-top: 4px; + padding-bottom: 4px; + } + + .username { + flex-grow: 1; + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + padding-right: 8px; + } + + .edit { + position: absolute; + display: flex; + align-items: flex-end; + right: 0; + bottom: 0; + } + + .name { + font-size: 1.25em; + color: #444444; + } + + .handle { + font-size: 1em; + color: #444444; + } + + .domain { + font-size: 1em; + color: #444444; + } +`; diff --git a/net/web/src/User/SideBar/Identity/useIdentity.hook.js b/net/web/src/User/SideBar/Identity/useIdentity.hook.js new file mode 100644 index 00000000..d111ab09 --- /dev/null +++ b/net/web/src/User/SideBar/Identity/useIdentity.hook.js @@ -0,0 +1,42 @@ +import { useContext, useState, useEffect } from 'react'; +import { AppContext } from '../../../AppContext/AppContext'; + +export function useIdentity() { + + const [state, setState] = useState({ + name: '', + handle: '', + domain: '', + imageUrl: '' + }); + + const actions = { + logout: async () => { + app.actions.logout() + } + }; + + const app = useContext(AppContext); + + const updateState = (value) => { + setState((s) => ({ ...s, ...value })); + } + + useEffect(() => { + if (app?.state?.Data?.profile) { + let profile = app.state.Data.profile; + if (profile.name != null) { + updateState({ name: profile.name }); + } + if (profile.image != null) { + updateState({ imageUrl: 'https://' + profile.node + '/profile/image?token=' + app.state.token }) + } else { + updateState({ imageUrl: '' }) + } + updateState({ handle: profile.handle }); + updateState({ domain: profile.node }); + } + }, [app]) + + return { state, actions }; +} diff --git a/net/web/src/User/SideBar/SideBar.jsx b/net/web/src/User/SideBar/SideBar.jsx new file mode 100644 index 00000000..8a48a8b8 --- /dev/null +++ b/net/web/src/User/SideBar/SideBar.jsx @@ -0,0 +1,12 @@ +import React from 'react' +import { SideBarWrapper } from './SideBar.styled'; +import { Identity } from './Identity/Identity'; + +export function SideBar() { + + return ( + + + + ) +} diff --git a/net/web/src/User/SideBar/SideBar.styled.js b/net/web/src/User/SideBar/SideBar.styled.js new file mode 100644 index 00000000..3fb04ebf --- /dev/null +++ b/net/web/src/User/SideBar/SideBar.styled.js @@ -0,0 +1,11 @@ +import styled from 'styled-components'; + +export const SideBarWrapper = styled.div` + width: 30%; + height: 100%; + max-width: 300px; + min-width: 200px; + border: 1px solid #8fbea7; + display: flex; + flex-direction: column; +`; diff --git a/net/web/src/User/User.jsx b/net/web/src/User/User.jsx index 689a4573..fe88aecc 100644 --- a/net/web/src/User/User.jsx +++ b/net/web/src/User/User.jsx @@ -2,6 +2,7 @@ import React from 'react' import { useUser } from './useUser.hook'; import { Button } from 'antd'; import { UserWrapper } from './User.styled'; +import { SideBar } from './SideBar/SideBar'; import connect from '../connect.png'; @@ -11,11 +12,10 @@ export function User() { return ( -
- -
+
+
) diff --git a/net/web/src/User/User.styled.js b/net/web/src/User/User.styled.js index 4bc3fa1f..6b3323bc 100644 --- a/net/web/src/User/User.styled.js +++ b/net/web/src/User/User.styled.js @@ -8,16 +8,9 @@ export const UserWrapper = styled.div` height: 100%; background-color: #f6f5ed; - .listing { - width: 30%; - height: 100%; - max-width: 300px; - min-width: 200px; - border: 1px solid #8fbea7; - } - .canvas { display: flex; + flex-direction: column; flex-grow: 1; height: 100%; border: 1px solid #8fbea7; diff --git a/net/web/src/User/useUser.hook.js b/net/web/src/User/useUser.hook.js index 239de300..c2dc086c 100644 --- a/net/web/src/User/useUser.hook.js +++ b/net/web/src/User/useUser.hook.js @@ -9,8 +9,6 @@ export function useUser() { const navigate = useNavigate(); const app = useContext(AppContext); -console.log(app); - const actions = { onLogout: async () => { app.actions.logout()