mirror of
https://github.com/mgerb/go-discord-bot
synced 2026-01-11 01:22:48 +00:00
UI Overhaul
This commit is contained in:
@@ -1,90 +0,0 @@
|
||||
import React from 'react';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import jwt_decode from 'jwt-decode';
|
||||
import { OauthService, StorageService } from '../../services';
|
||||
import './Navbar.scss';
|
||||
|
||||
interface Props {}
|
||||
|
||||
interface State {
|
||||
token: string | null;
|
||||
email?: string;
|
||||
oauthUrl?: string;
|
||||
}
|
||||
|
||||
export class Navbar extends React.Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
token: null,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.loadOauthUrl();
|
||||
const token = StorageService.getJWT();
|
||||
|
||||
if (token) {
|
||||
const claims: any = jwt_decode(token);
|
||||
const email = claims['email'];
|
||||
this.setState({ token, email });
|
||||
}
|
||||
}
|
||||
|
||||
async loadOauthUrl() {
|
||||
try {
|
||||
const oauthUrl = await OauthService.getOauthUrl();
|
||||
this.setState({ oauthUrl });
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
private logout = () => {
|
||||
StorageService.clear();
|
||||
window.location.href = '/';
|
||||
};
|
||||
|
||||
renderLoginButton() {
|
||||
if (!this.state.oauthUrl) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return !this.state.token ? (
|
||||
<a href={this.state.oauthUrl} className="Navbar__item">
|
||||
Login
|
||||
</a>
|
||||
) : (
|
||||
<a className="Navbar__item" onClick={this.logout}>
|
||||
Logout
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="Navbar">
|
||||
<div className="Navbar__header">Sound Bot</div>
|
||||
<NavLink exact to="/" className="Navbar__item" activeClassName="Navbar__item--active">
|
||||
Home
|
||||
</NavLink>
|
||||
<NavLink to="/soundboard" className="Navbar__item" activeClassName="Navbar__item--active">
|
||||
Soundboard
|
||||
</NavLink>
|
||||
<NavLink to="/downloader" className="Navbar__item" activeClassName="Navbar__item--active">
|
||||
Youtube Downloader
|
||||
</NavLink>
|
||||
<NavLink to="/clips" className="Navbar__item" activeClassName="Navbar__item--active">
|
||||
Clips
|
||||
</NavLink>
|
||||
<NavLink to="/stats" className="Navbar__item" activeClassName="Navbar__item--active">
|
||||
Stats
|
||||
</NavLink>
|
||||
|
||||
{this.renderLoginButton()}
|
||||
|
||||
{this.state.email && <div className="Navbar__email">{this.state.email}</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
export * from './Navbar';
|
||||
@@ -1 +0,0 @@
|
||||
export * from './SoundList';
|
||||
37
client/app/components/header/header.scss
Normal file
37
client/app/components/header/header.scss
Normal file
@@ -0,0 +1,37 @@
|
||||
@import '../../scss/variables';
|
||||
|
||||
.header {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background: linear-gradient(to right, $primaryBlue, darken($primaryBlue, 20%));
|
||||
height: 50px;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding-right: 20px;
|
||||
|
||||
&__title-container {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
&__nav-button {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
margin-right: 10px;
|
||||
background: $primaryBlue;
|
||||
border: none;
|
||||
color: $white;
|
||||
border-right: 1px solid darken($primaryBlue, 2%);
|
||||
cursor: pointer;
|
||||
outline: none;
|
||||
&:hover {
|
||||
background: darken($primaryBlue, 5%);
|
||||
}
|
||||
}
|
||||
}
|
||||
26
client/app/components/header/header.tsx
Normal file
26
client/app/components/header/header.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import React from 'react';
|
||||
import './header.scss';
|
||||
|
||||
interface IProps {
|
||||
onButtonClick: () => void;
|
||||
}
|
||||
|
||||
export class Header extends React.Component<IProps, any> {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div className="header">
|
||||
<div className="header__title-container">
|
||||
<button className="header__nav-button" onClick={this.props.onButtonClick}>
|
||||
<i className="fa fa-lg fa-bars" />
|
||||
</button>
|
||||
<h2 style={{ margin: 0 }}>Sound Bot</h2>
|
||||
</div>
|
||||
<a href="https://github.com/mgerb/go-discord-bot" className="fa fa-lg fa-github" target="_blank" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
4
client/app/components/index.ts
Normal file
4
client/app/components/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from './header/header';
|
||||
export * from './navbar/navbar';
|
||||
export * from './sound-list/sound-list';
|
||||
export * from './uploader/uploader';
|
||||
@@ -1,31 +1,25 @@
|
||||
@import '../../scss/variables';
|
||||
|
||||
.Navbar {
|
||||
.navbar {
|
||||
position: fixed;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
top: 0;
|
||||
left: 0;
|
||||
height: 100%;
|
||||
left: -$navbarWidth;
|
||||
top: 50px;
|
||||
height: calc(100% - 50px);
|
||||
width: $navbarWidth;
|
||||
background-color: $gray2;
|
||||
border-right: 1px solid darken($gray2, 2%);
|
||||
overflow-y: auto;
|
||||
padding-bottom: 10px;
|
||||
transition: 0.2s left ease-in-out;
|
||||
|
||||
&--open {
|
||||
left: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.Navbar__header {
|
||||
font-size: 25px;
|
||||
min-height: 100px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
background-color: $primaryBlue;
|
||||
border-bottom: 1px solid $gray3;
|
||||
}
|
||||
|
||||
.Navbar__item {
|
||||
.navbar__item {
|
||||
min-height: 50px;
|
||||
text-decoration: none;
|
||||
display: flex;
|
||||
@@ -39,18 +33,18 @@
|
||||
background-color: $gray1;
|
||||
}
|
||||
|
||||
& + .Navbar__item {
|
||||
& + & {
|
||||
border-top: 1px solid $gray3;
|
||||
}
|
||||
}
|
||||
|
||||
.Navbar__item--active {
|
||||
.navbar__item--active {
|
||||
padding-left: 4px;
|
||||
border-right: 4px solid $primaryBlue;
|
||||
color: $primaryBlue !important;
|
||||
}
|
||||
|
||||
.Navbar__email {
|
||||
.navbar__email {
|
||||
padding-top: 10px;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
86
client/app/components/navbar/navbar.tsx
Normal file
86
client/app/components/navbar/navbar.tsx
Normal file
@@ -0,0 +1,86 @@
|
||||
import React from 'react';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import { IClaims } from '../../model';
|
||||
import { OauthService, StorageService } from '../../services';
|
||||
import './navbar.scss';
|
||||
|
||||
interface Props {
|
||||
claims?: IClaims;
|
||||
open: boolean;
|
||||
onNavClick: () => void;
|
||||
}
|
||||
|
||||
interface State {
|
||||
oauthUrl?: string;
|
||||
}
|
||||
|
||||
export class Navbar extends React.Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.loadOauthUrl();
|
||||
}
|
||||
|
||||
async loadOauthUrl() {
|
||||
const oauthUrl = await OauthService.getOauthUrl();
|
||||
if (oauthUrl) {
|
||||
this.setState({ oauthUrl });
|
||||
}
|
||||
}
|
||||
|
||||
private logout = () => {
|
||||
StorageService.clear();
|
||||
window.location.href = '/';
|
||||
};
|
||||
|
||||
renderLoginButton() {
|
||||
const { claims } = this.props;
|
||||
|
||||
if (!this.state.oauthUrl) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return !claims ? (
|
||||
<a href={this.state.oauthUrl} className="navbar__item">
|
||||
Login
|
||||
</a>
|
||||
) : (
|
||||
<a className="navbar__item" onClick={this.logout}>
|
||||
Logout
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
renderNavLink = (title: string, to: string, params?: any) => {
|
||||
return (
|
||||
<NavLink
|
||||
{...params}
|
||||
to={to}
|
||||
className="navbar__item"
|
||||
activeClassName="navbar__item--active"
|
||||
onClick={this.props.onNavClick}
|
||||
>
|
||||
{title}
|
||||
</NavLink>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { claims, open } = this.props;
|
||||
const openClass = open ? 'navbar--open' : '';
|
||||
return (
|
||||
<div className={'navbar ' + openClass}>
|
||||
{this.renderNavLink('Soundboard', '/', { exact: true })}
|
||||
{this.renderNavLink('Youtube Downloader', '/downloader')}
|
||||
{this.renderNavLink('Clips', '/clips')}
|
||||
{this.renderNavLink('Stats', '/stats')}
|
||||
{this.renderLoginButton()}
|
||||
|
||||
{claims && claims.email && <div className="navbar__email">{claims.email}</div>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,12 @@
|
||||
@import '../../scss/variables';
|
||||
|
||||
.SoundList__item {
|
||||
.sound-list__item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
height: 50px;
|
||||
|
||||
& + .SoundList__item {
|
||||
& + .sound-list__item {
|
||||
border-top: 1px solid $gray3;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,5 @@
|
||||
import React from 'react';
|
||||
|
||||
import './SoundList.scss';
|
||||
import './sound-list.scss';
|
||||
|
||||
interface Props {
|
||||
soundList: SoundType[];
|
||||
@@ -64,8 +63,8 @@ export class SoundList extends React.Component<Props, State> {
|
||||
{soundList.length > 0
|
||||
? soundList.map((sound: SoundType, index: number) => {
|
||||
return (
|
||||
<div key={index} className="SoundList__item">
|
||||
<div>{(sound.prefix || '') + sound.name}</div>
|
||||
<div key={index} className="sound-list__item">
|
||||
<div className="text-wrap">{(sound.prefix || '') + sound.name}</div>
|
||||
|
||||
{this.checkExtension(sound.extension) && this.state.showAudioControls[index] ? (
|
||||
<audio
|
||||
22
client/app/components/uploader/uploader.scss
Normal file
22
client/app/components/uploader/uploader.scss
Normal file
@@ -0,0 +1,22 @@
|
||||
@import '../../scss/variables';
|
||||
|
||||
.dropzone {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 2px solid $primaryBlue;
|
||||
border-radius: 1em;
|
||||
max-width: 800px;
|
||||
margin-bottom: 20px;
|
||||
padding: 20px;
|
||||
color: $lightGray;
|
||||
background-color: $gray2;
|
||||
transition: box-shadow 0.1s linear, background-color 0.1s linear;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.dropzone--active {
|
||||
background-color: $gray3;
|
||||
box-shadow: 0px 0px 5px 1px $primaryBlue;
|
||||
}
|
||||
86
client/app/components/uploader/uploader.tsx
Normal file
86
client/app/components/uploader/uploader.tsx
Normal file
@@ -0,0 +1,86 @@
|
||||
import React from 'react';
|
||||
import Dropzone from 'react-dropzone';
|
||||
import { axios } from '../../services';
|
||||
import './uploader.scss';
|
||||
|
||||
interface IProps {
|
||||
onComplete: () => void;
|
||||
}
|
||||
|
||||
interface IState {
|
||||
percentCompleted: number;
|
||||
uploaded: boolean;
|
||||
uploadError: string;
|
||||
}
|
||||
|
||||
export class Uploader extends React.Component<IProps, IState> {
|
||||
constructor(props: IProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
percentCompleted: 0,
|
||||
uploaded: false,
|
||||
uploadError: ' ',
|
||||
};
|
||||
}
|
||||
|
||||
private config = {
|
||||
headers: {
|
||||
'Content-Type': 'multipart/form-data',
|
||||
},
|
||||
onUploadProgress: (progressEvent: any) => {
|
||||
this.setState({
|
||||
percentCompleted: Math.round((progressEvent.loaded * 100) / progressEvent.total),
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
onDrop = (acceptedFiles: any) => {
|
||||
if (acceptedFiles.length > 0) {
|
||||
this.uploadFile(acceptedFiles[0]);
|
||||
}
|
||||
};
|
||||
|
||||
uploadFile(file: any) {
|
||||
let formData = new FormData();
|
||||
formData.append('name', file.name);
|
||||
formData.append('file', file);
|
||||
|
||||
axios
|
||||
.post('/api/upload', formData, this.config)
|
||||
.then(() => {
|
||||
this.setState({
|
||||
percentCompleted: 0,
|
||||
uploaded: true,
|
||||
uploadError: ' ',
|
||||
});
|
||||
|
||||
this.props.onComplete();
|
||||
})
|
||||
.catch(err => {
|
||||
this.setState({
|
||||
percentCompleted: 0,
|
||||
uploaded: false,
|
||||
uploadError: err.response.data,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Dropzone
|
||||
className="dropzone"
|
||||
activeClassName="dropzone--active"
|
||||
onDrop={this.onDrop}
|
||||
multiple={false}
|
||||
disableClick={false}
|
||||
maxSize={10000000000}
|
||||
accept={'audio/*'}
|
||||
>
|
||||
<div style={{ fontSize: '20px' }}>Click or drop file to upload</div>
|
||||
{this.state.percentCompleted > 0 ? <div>Uploading: {this.state.percentCompleted}</div> : ''}
|
||||
{this.state.uploaded ? <div style={{ color: 'green' }}>File uploded!</div> : ''}
|
||||
<div style={{ color: '#f95f59' }}>{this.state.uploadError}</div>
|
||||
</Dropzone>
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user