mirror of
https://github.com/balzack/databag.git
synced 2025-05-05 07:55:15 +00:00
rendering profile in sidebar
This commit is contained in:
parent
68c6acaf64
commit
1d3d1a44a8
@ -11,7 +11,7 @@ import (
|
|||||||
func GetProfileImage(w http.ResponseWriter, r *http.Request) {
|
func GetProfileImage(w http.ResponseWriter, r *http.Request) {
|
||||||
var data []byte
|
var data []byte
|
||||||
|
|
||||||
account, code, err := BearerAppToken(r, true);
|
account, code, err := ParamAppToken(r, true);
|
||||||
if err != nil {
|
if err != nil {
|
||||||
ErrResponse(w, code, err)
|
ErrResponse(w, code, err)
|
||||||
return
|
return
|
||||||
|
@ -77,6 +77,40 @@ func BearerAccountToken(r *http.Request) (*store.AccountToken, error) {
|
|||||||
return &accountToken, nil
|
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) {
|
func BearerAppToken(r *http.Request, detail bool) (*store.Account, int, error) {
|
||||||
|
|
||||||
// parse bearer authentication
|
// parse bearer authentication
|
||||||
|
@ -9,6 +9,7 @@ func getProfileModel(account *store.Account) *Profile {
|
|||||||
return &Profile{
|
return &Profile{
|
||||||
Guid: account.Guid,
|
Guid: account.Guid,
|
||||||
Handle: account.Username,
|
Handle: account.Username,
|
||||||
|
Name: account.AccountDetail.Name,
|
||||||
Description: account.AccountDetail.Description,
|
Description: account.AccountDetail.Description,
|
||||||
Location: account.AccountDetail.Location,
|
Location: account.AccountDetail.Location,
|
||||||
Image: account.AccountDetail.Image,
|
Image: account.AccountDetail.Image,
|
||||||
|
@ -102,7 +102,7 @@ func TestProfileUpdate(t *testing.T) {
|
|||||||
APP_TOKENAPP, set.A.Token, &profile, nil))
|
APP_TOKENAPP, set.A.Token, &profile, nil))
|
||||||
|
|
||||||
// retrieve profile image
|
// 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)
|
APP_TOKENAPP, set.A.Token, 0, 0)
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
@ -7,6 +7,12 @@ export function Login(props) {
|
|||||||
|
|
||||||
const { state, actions } = useLogin()
|
const { state, actions } = useLogin()
|
||||||
|
|
||||||
|
const keyDown = (e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
actions.onLogin()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return(
|
return(
|
||||||
<LoginWrapper>
|
<LoginWrapper>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
@ -15,9 +21,9 @@ export function Login(props) {
|
|||||||
<span class="subheader-text">Communication for the Decentralized Web</span>
|
<span class="subheader-text">Communication for the Decentralized Web</span>
|
||||||
</div>
|
</div>
|
||||||
<LoginInput size="large" spellCheck="false" placeholder="username" prefix={<UserOutlined />}
|
<LoginInput size="large" spellCheck="false" placeholder="username" prefix={<UserOutlined />}
|
||||||
onChange={(e) => actions.setUsername(e.target.value)} value={state.username} />
|
onChange={(e) => actions.setUsername(e.target.value)} value={state.username} onKeyDown={(e) => keyDown(e)}/>
|
||||||
<LoginPassword size="large" spellCheck="false" placeholder="password" prefix={<LockOutlined />}
|
<LoginPassword size="large" spellCheck="false" placeholder="password" prefix={<LockOutlined />}
|
||||||
onChange={(e) => actions.setPassword(e.target.value)} value={state.password} />
|
onChange={(e) => actions.setPassword(e.target.value)} value={state.password} onKeyDown={(e) => keyDown(e)}/>
|
||||||
<LoginEnter type="primary" onClick={() => actions.onLogin()} disabled={actions.isDisabled()}>
|
<LoginEnter type="primary" onClick={() => actions.onLogin()} disabled={actions.isDisabled()}>
|
||||||
<span>Sign In</span>
|
<span>Sign In</span>
|
||||||
</LoginEnter>
|
</LoginEnter>
|
||||||
|
@ -28,7 +28,7 @@ export function useLogin() {
|
|||||||
return false
|
return false
|
||||||
},
|
},
|
||||||
onLogin: async () => {
|
onLogin: async () => {
|
||||||
if (!state.spinning) {
|
if (!state.spinning && state.username != '' && state.password != '') {
|
||||||
actions.updateState({ spinning: true })
|
actions.updateState({ spinning: true })
|
||||||
try {
|
try {
|
||||||
await app.actions.login(state.username, state.password)
|
await app.actions.login(state.username, state.password)
|
||||||
|
48
net/web/src/User/SideBar/Identity/Identity.jsx
Normal file
48
net/web/src/User/SideBar/Identity/Identity.jsx
Normal file
@ -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 <Avatar size={64} icon={<UserOutlined />} />
|
||||||
|
}
|
||||||
|
return <Avatar size={64} src={<Image preview={false} src={ state.imageUrl } style={{ width: 64 }} />} />
|
||||||
|
}
|
||||||
|
|
||||||
|
const menu = (
|
||||||
|
<Menu>
|
||||||
|
<Menu.Item key="0">
|
||||||
|
<div onClick={() => {}}>Edit Profile</div>
|
||||||
|
</Menu.Item>
|
||||||
|
<Menu.Item key="1">
|
||||||
|
<div onClick={() => actions.logout()}>Sign Out</div>
|
||||||
|
</Menu.Item>
|
||||||
|
</Menu>
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<IdentityWrapper>
|
||||||
|
<Dropdown overlay={menu} overlayStyle={{ minWidth: 0 }} trigger={['click']} placement="bottomRight">
|
||||||
|
<div>
|
||||||
|
<div class="container">
|
||||||
|
<div class="logo">
|
||||||
|
<Logo />
|
||||||
|
</div>
|
||||||
|
<div class="username">
|
||||||
|
<span class="name">{ state.name }</span>
|
||||||
|
<span class="handle">{ state.handle }</span>
|
||||||
|
</div>
|
||||||
|
<DownOutlined />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Dropdown>
|
||||||
|
</IdentityWrapper>
|
||||||
|
)
|
||||||
|
}
|
68
net/web/src/User/SideBar/Identity/Identity.styled.js
Normal file
68
net/web/src/User/SideBar/Identity/Identity.styled.js
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
`;
|
42
net/web/src/User/SideBar/Identity/useIdentity.hook.js
Normal file
42
net/web/src/User/SideBar/Identity/useIdentity.hook.js
Normal file
@ -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 };
|
||||||
|
}
|
12
net/web/src/User/SideBar/SideBar.jsx
Normal file
12
net/web/src/User/SideBar/SideBar.jsx
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import { SideBarWrapper } from './SideBar.styled';
|
||||||
|
import { Identity } from './Identity/Identity';
|
||||||
|
|
||||||
|
export function SideBar() {
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SideBarWrapper>
|
||||||
|
<Identity />
|
||||||
|
</SideBarWrapper>
|
||||||
|
)
|
||||||
|
}
|
11
net/web/src/User/SideBar/SideBar.styled.js
Normal file
11
net/web/src/User/SideBar/SideBar.styled.js
Normal file
@ -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;
|
||||||
|
`;
|
@ -2,6 +2,7 @@ import React from 'react'
|
|||||||
import { useUser } from './useUser.hook';
|
import { useUser } from './useUser.hook';
|
||||||
import { Button } from 'antd';
|
import { Button } from 'antd';
|
||||||
import { UserWrapper } from './User.styled';
|
import { UserWrapper } from './User.styled';
|
||||||
|
import { SideBar } from './SideBar/SideBar';
|
||||||
import connect from '../connect.png';
|
import connect from '../connect.png';
|
||||||
|
|
||||||
|
|
||||||
@ -11,11 +12,10 @@ export function User() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<UserWrapper>
|
<UserWrapper>
|
||||||
<div class="listing">
|
<SideBar />
|
||||||
<Button type="primary" onClick={() => actions.onLogout()} style={{ alignSelf: 'center', marginTop: '16px', width: '33%' }}>Sign Out</Button>
|
|
||||||
</div>
|
|
||||||
<div class="canvas">
|
<div class="canvas">
|
||||||
<img class="connect" src={connect} alt="" />
|
<img class="connect" src={connect} alt="" />
|
||||||
|
<Button type="primary" onClick={() => actions.onLogout()} style={{ alignSelf: 'center', marginTop: '16px', width: '33%' }}>Sign Out</Button>
|
||||||
</div>
|
</div>
|
||||||
</UserWrapper>
|
</UserWrapper>
|
||||||
)
|
)
|
||||||
|
@ -8,16 +8,9 @@ export const UserWrapper = styled.div`
|
|||||||
height: 100%;
|
height: 100%;
|
||||||
background-color: #f6f5ed;
|
background-color: #f6f5ed;
|
||||||
|
|
||||||
.listing {
|
|
||||||
width: 30%;
|
|
||||||
height: 100%;
|
|
||||||
max-width: 300px;
|
|
||||||
min-width: 200px;
|
|
||||||
border: 1px solid #8fbea7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.canvas {
|
.canvas {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
flex-grow: 1;
|
flex-grow: 1;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
border: 1px solid #8fbea7;
|
border: 1px solid #8fbea7;
|
||||||
|
@ -9,8 +9,6 @@ export function useUser() {
|
|||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const app = useContext(AppContext);
|
const app = useContext(AppContext);
|
||||||
|
|
||||||
console.log(app);
|
|
||||||
|
|
||||||
const actions = {
|
const actions = {
|
||||||
onLogout: async () => {
|
onLogout: async () => {
|
||||||
app.actions.logout()
|
app.actions.logout()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user