mirror of
https://github.com/mgerb/classic-wow-forums
synced 2026-01-10 17:12:48 +00:00
client - wip user account page
This commit is contained in:
@@ -40,7 +40,7 @@ export const initializeAxios = (): Promise<void> => {
|
|||||||
|
|
||||||
|
|
||||||
export function setAuthorizationHeader(jwt: string): void {
|
export function setAuthorizationHeader(jwt: string): void {
|
||||||
ax.defaults.headers.common['Authorization'] = jwt;
|
ax.defaults.headers.common['Authorization'] = `Bearer ${jwt}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resetAuthorizationHeader(): void {
|
export function resetAuthorizationHeader(): void {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import './content-container.scss';
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
className?: string;
|
className?: string;
|
||||||
|
style?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface State {}
|
interface State {}
|
||||||
@@ -15,7 +16,7 @@ export class ContentContainer extends React.Component<Props, State> {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className={`content-container ${this.props.className}`}>
|
<div className={`content-container ${this.props.className}`} style={this.props.style}>
|
||||||
<div className="border-container">
|
<div className="border-container">
|
||||||
<div className="border border__left"/>
|
<div className="border border__left"/>
|
||||||
<div className="border border__right"/>
|
<div className="border border__right"/>
|
||||||
|
|||||||
@@ -1,8 +1,10 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { RouteComponentProps } from 'react-router-dom';
|
||||||
import { inject, observer } from 'mobx-react';
|
import { inject, observer } from 'mobx-react';
|
||||||
|
import { Portrait } from '../portrait/portrait';
|
||||||
import { UserStore } from '../../stores/user-store';
|
import { UserStore } from '../../stores/user-store';
|
||||||
|
|
||||||
interface Props {
|
interface Props extends RouteComponentProps<any> {
|
||||||
className?: string;
|
className?: string;
|
||||||
userStore?: UserStore;
|
userStore?: UserStore;
|
||||||
}
|
}
|
||||||
@@ -23,13 +25,28 @@ export class LoginButton extends React.Component<Props, State> {
|
|||||||
window.open(oauthUrl, '_blank', 'resizeable=yes, height=900, width=1200');
|
window.open(oauthUrl, '_blank', 'resizeable=yes, height=900, width=1200');
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
renderPortrait() {
|
||||||
return (
|
return (
|
||||||
<div className={this.props.className}>
|
<div onClick={() => this.props.history.push('/user-account')} style={{ cursor: 'pointer' }}>
|
||||||
|
<Portrait imageSrc={require('../../assets/Tyren.gif')}/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderLoginButton() {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
<img src={require('../../assets/login-bot-left.gif')} />
|
<img src={require('../../assets/login-bot-left.gif')} />
|
||||||
<img src={require('../../assets/login-bot-login.gif')} style={{ cursor: 'pointer' }} onClick={this.login.bind(this)} />
|
<img src={require('../../assets/login-bot-login.gif')} style={{ cursor: 'pointer' }} onClick={this.login.bind(this)} />
|
||||||
<img src={require('../../assets/login-bot-right.gif')} />
|
<img src={require('../../assets/login-bot-right.gif')} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
render() {
|
||||||
|
return (
|
||||||
|
<div className={this.props.className}>
|
||||||
|
{this.props.userStore!.user ? this.renderPortrait() : this.renderLoginButton()}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import './portrait.scss';
|
|||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
imageSrc: any;
|
imageSrc: any;
|
||||||
|
style?: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
@@ -14,7 +15,7 @@ export class Portrait extends React.Component<Props, State> {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
return (
|
return (
|
||||||
<div className="portrait">
|
<div className="portrait" style={this.props.style}>
|
||||||
<img src={require('../../assets/portrait-top.gif')}/>
|
<img src={require('../../assets/portrait-top.gif')}/>
|
||||||
<div>
|
<div>
|
||||||
<img src={require('../../assets/level-circle.gif')} className="portrait__level-circle"/>
|
<img src={require('../../assets/level-circle.gif')} className="portrait__level-circle"/>
|
||||||
|
|||||||
71
client/app/model/avatar.ts
Normal file
71
client/app/model/avatar.ts
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
export interface AvatarModel {
|
||||||
|
title: string;
|
||||||
|
imageSrc: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const AvatarList: AvatarModel[] = [
|
||||||
|
{
|
||||||
|
title: 'dwarf_f',
|
||||||
|
imageSrc: require('../assets/avatars/Dwarf_female.gif'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'dwarf_m',
|
||||||
|
imageSrc: require('../assets/avatars/Dwarf_male.gif'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'gnome_f',
|
||||||
|
imageSrc: require('../assets/avatars/Gnome_female.gif'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'gnome_m',
|
||||||
|
imageSrc: require('../assets/avatars/Gnome_male.gif'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'human_f',
|
||||||
|
imageSrc: require('../assets/avatars/Human_female.gif'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'human_m',
|
||||||
|
imageSrc: require('../assets/avatars/Human_male.gif'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'night_elf_f',
|
||||||
|
imageSrc: require('../assets/avatars/Night_elf_female.gif'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'night_elf_m',
|
||||||
|
imageSrc: require('../assets/avatars/Night_elf_male.gif'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'orc_f',
|
||||||
|
imageSrc: require('../assets/avatars/Orc_female.gif'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'orc_m',
|
||||||
|
imageSrc: require('../assets/avatars/Orc_male.gif'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'tauren_f',
|
||||||
|
imageSrc: require('../assets/avatars/Tauren_female.gif'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'tauren_m',
|
||||||
|
imageSrc: require('../assets/avatars/Tauren_male.gif'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'troll_f',
|
||||||
|
imageSrc: require('../assets/avatars/Troll_female.gif'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'troll_m',
|
||||||
|
imageSrc: require('../assets/avatars/Troll_male.gif'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'undead_f',
|
||||||
|
imageSrc: require('../assets/avatars/Undead_female.gif'),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'undead_m',
|
||||||
|
imageSrc: require('../assets/avatars/Undead_male.gif'),
|
||||||
|
},
|
||||||
|
];
|
||||||
13
client/app/model/character.ts
Normal file
13
client/app/model/character.ts
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
export interface CharacterModel {
|
||||||
|
achievementPoints: number;
|
||||||
|
battlegroup: string;
|
||||||
|
class: number;
|
||||||
|
gender: number;
|
||||||
|
guild: string;
|
||||||
|
lastModified: number;
|
||||||
|
level: number;
|
||||||
|
name: string;
|
||||||
|
race: number;
|
||||||
|
realm: string;
|
||||||
|
spec: any;
|
||||||
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
|
export * from './avatar';
|
||||||
export * from './category';
|
export * from './category';
|
||||||
|
export * from './character';
|
||||||
export * from './reply';
|
export * from './reply';
|
||||||
export * from './thread';
|
export * from './thread';
|
||||||
export * from './user';
|
export * from './user';
|
||||||
|
|||||||
@@ -5,4 +5,9 @@ export interface UserModel {
|
|||||||
id: number;
|
id: number;
|
||||||
permissions: string;
|
permissions: string;
|
||||||
token: string;
|
token: string;
|
||||||
|
character_name?: string;
|
||||||
|
character_class?: string;
|
||||||
|
character_guild?: string;
|
||||||
|
character_avatar?: string;
|
||||||
|
character_realm?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ export class Forum extends React.Component<Props, State> {
|
|||||||
<div className="forum-header">
|
<div className="forum-header">
|
||||||
<ForumNav />
|
<ForumNav />
|
||||||
<div style={{ height: '100%' }}>
|
<div style={{ height: '100%' }}>
|
||||||
<LoginButton/>
|
<LoginButton {...this.props}/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
20
client/app/pages/user-account/user-account.scss
Normal file
20
client/app/pages/user-account/user-account.scss
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
.avatar-list {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
padding: 20px;
|
||||||
|
padding-left: 10px;
|
||||||
|
|
||||||
|
&__item {
|
||||||
|
margin-left: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
transition: all 0.1s ease-in-out;
|
||||||
|
top: 0;
|
||||||
|
|
||||||
|
&:hover, &__selected {
|
||||||
|
top: -1px;
|
||||||
|
box-shadow: 0 0 10px #00C0FF;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,18 +1,135 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { inject, observer } from 'mobx-react';
|
||||||
import { RouteComponentProps } from 'react-router-dom';
|
import { RouteComponentProps } from 'react-router-dom';
|
||||||
import { ContentContainer } from '../../components';
|
import { get, groupBy, map } from 'lodash';
|
||||||
|
import { ContentContainer, Portrait, ScrollToTop } from '../../components';
|
||||||
|
import { UserStore } from '../../stores/user-store';
|
||||||
|
import { UserService } from '../../services';
|
||||||
|
import { AvatarList, CharacterModel } from '../../model';
|
||||||
|
import './user-account.scss';
|
||||||
|
|
||||||
interface Props extends RouteComponentProps<any> {}
|
interface Props extends RouteComponentProps<any> {
|
||||||
|
userStore?: UserStore;
|
||||||
|
}
|
||||||
|
|
||||||
interface State {}
|
interface State {
|
||||||
|
characters: {[realm: string]: CharacterModel[]};
|
||||||
|
selectedRealm?: string;
|
||||||
|
selectedCharIndex: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
@inject('userStore')
|
||||||
|
@observer
|
||||||
export class UserAccount extends React.Component<Props, State> {
|
export class UserAccount extends React.Component<Props, State> {
|
||||||
|
constructor(props: Props) {
|
||||||
|
super(props);
|
||||||
|
this.state = {
|
||||||
|
characters: {},
|
||||||
|
selectedCharIndex: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
componentDidMount() {}
|
componentDidMount() {
|
||||||
|
this.getCharacters();
|
||||||
|
}
|
||||||
|
|
||||||
|
private selectedCharacter(): CharacterModel {
|
||||||
|
const { selectedRealm, selectedCharIndex } = this.state;
|
||||||
|
const char = get(this.state, `characters[${selectedRealm}][${selectedCharIndex}]`);
|
||||||
|
return char;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCharacters() {
|
||||||
|
try {
|
||||||
|
const res = await UserService.getCharacters() as any;
|
||||||
|
const characters = groupBy(res, 'realm');
|
||||||
|
this.setState({
|
||||||
|
characters,
|
||||||
|
selectedRealm: res[0].realm,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onRealmSelect(event: any) {
|
||||||
|
this.setState({ selectedRealm: event.target.value, selectedCharIndex: 0 });
|
||||||
|
}
|
||||||
|
|
||||||
|
onCharSelect(event: any) {
|
||||||
|
this.setState({ selectedCharIndex: event.target.value as any });
|
||||||
|
}
|
||||||
|
|
||||||
|
renderDropDowns() {
|
||||||
|
if (!this.selectedCharacter()) {
|
||||||
|
return <div></div>;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<div style={{ marginBottom: '10px' }}>
|
||||||
|
<select value={this.selectedCharacter().realm}
|
||||||
|
onChange={event => this.onRealmSelect(event)}>
|
||||||
|
{map(this.state.characters, (_, realm: string) => {
|
||||||
|
return <option key={`realm${realm}`}>{realm}</option>;
|
||||||
|
})}
|
||||||
|
</select>
|
||||||
|
<select style={{ marginLeft: '5px' }}
|
||||||
|
onChange={event => this.onCharSelect(event)}>
|
||||||
|
{map(this.state.characters[this.state.selectedRealm!], (value, index) => {
|
||||||
|
return <option key={this.state.selectedRealm! + index} value={index}>{value.name}</option>;
|
||||||
|
})}
|
||||||
|
</select>
|
||||||
|
|
||||||
|
<div className="avatar-list">
|
||||||
|
{AvatarList.map((val, index) => {
|
||||||
|
return (
|
||||||
|
<div key={index} className="avatar-list__item">
|
||||||
|
<img src={val.imageSrc}/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async onSave() {
|
||||||
|
const { name, guild, realm } = this.selectedCharacter();
|
||||||
|
const data = {
|
||||||
|
character_name: name,
|
||||||
|
character_class: 'Rogue', // todo get class from number
|
||||||
|
character_guild: guild,
|
||||||
|
character_realm: realm,
|
||||||
|
character_avatar: 'Avatar', // TODO:
|
||||||
|
};
|
||||||
|
|
||||||
|
await UserService.saveCharacter(data);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const { battletag, character_name, character_class, character_guild, character_realm } = this.props.userStore!.user!;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ContentContainer></ContentContainer>
|
<ScrollToTop>
|
||||||
|
<ContentContainer style={{ minHeight: '500px' }}>
|
||||||
|
<div className="flex">
|
||||||
|
<Portrait imageSrc={require('../../assets/Tyren.gif')}/>
|
||||||
|
<div style={{ paddingLeft: '10px' }}>
|
||||||
|
{battletag && <div><b>Battletag: </b>{battletag}</div>}
|
||||||
|
{character_name && <div><b>Character: </b>{character_name}</div>}
|
||||||
|
{character_class && <div><b>Class: </b>{character_class}</div>}
|
||||||
|
{character_guild && <div><b>Guild: </b>{character_guild}</div>}
|
||||||
|
{character_realm && <div><b>Realm: </b>{character_realm}</div>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<h2>Set a new default character</h2>
|
||||||
|
{this.renderDropDowns()}
|
||||||
|
<a onClick={() => this.onSave()}>Save</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</ContentContainer>
|
||||||
|
</ScrollToTop>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,41 @@
|
|||||||
import axios from '../axios/axios';
|
import axios from '../axios/axios';
|
||||||
import userStore from '../stores/user-store';
|
import userStore from '../stores/user-store';
|
||||||
|
import { CharacterModel } from '../model';
|
||||||
|
|
||||||
// fetch user and store in local storage
|
// fetch user and store in local storage
|
||||||
const authorize = async (code: string): Promise<void> => {
|
const authorize = async (code: string): Promise<void> => {
|
||||||
try {
|
try {
|
||||||
const res = await axios.post('/api/battlenet/authorize', { code });
|
const res = await axios.post('/api/user/authorize', { code });
|
||||||
userStore.setUser(res.data.data);
|
userStore.setUser(res.data.data);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getCharacters = async (): Promise<CharacterModel | null> => {
|
||||||
|
try {
|
||||||
|
const res = await axios.get('/api/user/characters');
|
||||||
|
return res.data.data.characters;
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveCharacter = async (character: any): Promise<any> => {
|
||||||
|
try {
|
||||||
|
const res = await axios.put('/api/user/characters', character);
|
||||||
|
const user = res.data.data;
|
||||||
|
userStore.setCharacterInfo(user);
|
||||||
|
return user;
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
export const UserService = {
|
export const UserService = {
|
||||||
authorize,
|
authorize,
|
||||||
|
saveCharacter,
|
||||||
|
getCharacters,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -13,11 +13,6 @@ export class UserStore {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@action setUser(user: UserModel) {
|
|
||||||
localStorage.setItem('user', JSON.stringify(user));
|
|
||||||
this.getUserFromStorage();
|
|
||||||
}
|
|
||||||
|
|
||||||
@action private getUserFromStorage(): void {
|
@action private getUserFromStorage(): void {
|
||||||
const u = localStorage.getItem('user');
|
const u = localStorage.getItem('user');
|
||||||
if (u) {
|
if (u) {
|
||||||
@@ -25,6 +20,23 @@ export class UserStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@action setUser(user: UserModel) {
|
||||||
|
localStorage.setItem('user', JSON.stringify(user));
|
||||||
|
this.getUserFromStorage();
|
||||||
|
}
|
||||||
|
|
||||||
|
@action public setCharacterInfo(info: {[key: string]: string}) {
|
||||||
|
const { character_avatar, character_class, character_guild, character_name, character_realm } = info;
|
||||||
|
const user = {...this.user!,
|
||||||
|
character_avatar,
|
||||||
|
character_class,
|
||||||
|
character_guild,
|
||||||
|
character_name,
|
||||||
|
character_realm,
|
||||||
|
};
|
||||||
|
this.setUser(user);
|
||||||
|
}
|
||||||
|
|
||||||
// when the user logs out
|
// when the user logs out
|
||||||
@action resetUser() {
|
@action resetUser() {
|
||||||
this.user = undefined;
|
this.user = undefined;
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ defmodule MyApp.Data.User do
|
|||||||
defp get_user(battle_net_id) do
|
defp get_user(battle_net_id) do
|
||||||
query = from u in "user",
|
query = from u in "user",
|
||||||
where: u.battle_net_id == ^battle_net_id,
|
where: u.battle_net_id == ^battle_net_id,
|
||||||
select: [:id, :permissions, :battle_net_id, :battletag]
|
select: [:id, :permissions, :battle_net_id, :battletag, :character_guild, :character_name, :character_class, :character_realm, :character_avatar]
|
||||||
Repo.one(query)
|
Repo.one(query)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user