added change password, style updates, api updates
This commit is contained in:
parent
14a3c0bab1
commit
6617160c75
@ -12,6 +12,7 @@
|
||||
# below:
|
||||
.git
|
||||
.gitignore
|
||||
.vscode
|
||||
|
||||
venv
|
||||
node_modules
|
||||
@ -19,4 +20,4 @@ node_modules
|
||||
# Python pycache:
|
||||
__pycache__/
|
||||
# Ignored by the build system
|
||||
/setup.cfg
|
||||
/setup.cfg
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -7,6 +7,7 @@ node_modules/
|
||||
*$py.class
|
||||
|
||||
.idea
|
||||
.vscode
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
@ -1,5 +1,6 @@
|
||||
from flask import Blueprint, session, request, jsonify
|
||||
from google.cloud import firestore
|
||||
from werkzeug.security import check_password_hash, generate_password_hash
|
||||
|
||||
blueprint = Blueprint('api', __name__)
|
||||
db = firestore.Client()
|
||||
@ -20,17 +21,64 @@ def get_playlists():
|
||||
error = {'error': 'multiple usernames?'}
|
||||
return jsonify(error), 500
|
||||
|
||||
docs = playlists.stream()
|
||||
|
||||
response = {
|
||||
'playlists': [i.to_dict() for i in docs]
|
||||
'playlists': [i.to_dict() for i in playlists.stream()]
|
||||
}
|
||||
|
||||
return jsonify(response)
|
||||
return jsonify(response), 200
|
||||
|
||||
else:
|
||||
error = {'error': 'username not in session, not logged in?'}
|
||||
return jsonify(error), 500
|
||||
error = {'error': 'not logged in'}
|
||||
return jsonify(error), 401
|
||||
|
||||
|
||||
@blueprint.route('/playlist', methods=['GET', 'POST', 'PUT'])
|
||||
def get_playlist():
|
||||
|
||||
if 'username' in session:
|
||||
|
||||
users = db.collection(u'spotify_users').where(u'username', u'==', session['username']).stream()
|
||||
|
||||
users = [i for i in users]
|
||||
|
||||
if len(users) == 1:
|
||||
playlists = db.document(u'spotify_users/{}'.format(users[0].id)).collection(u'playlists')
|
||||
else:
|
||||
error = {'error': 'multiple usernames?'}
|
||||
return jsonify(error), 500
|
||||
|
||||
if request.method == 'GET':
|
||||
playlist_name = request.args.get('name', None)
|
||||
|
||||
if playlist_name:
|
||||
|
||||
playlist = [i for i in playlists.where(u'name', u'==', playlist_name).stream()]
|
||||
|
||||
if len(playlist) == 0:
|
||||
return jsonify({'error': 'no playlist found'}), 404
|
||||
elif len(playlist) > 1:
|
||||
return jsonify({'error': 'multiple playlists found'}), 500
|
||||
|
||||
return jsonify(playlist[0].to_dict()), 200
|
||||
|
||||
else:
|
||||
response = {"error": 'no name requested'}
|
||||
return jsonify(response), 400
|
||||
|
||||
elif request.method == 'POST' or request.method == 'PUT':
|
||||
|
||||
request_json = request.get_json()
|
||||
|
||||
if 'name' not in request_json:
|
||||
return jsonify({'error': "no name provided"}), 400
|
||||
|
||||
playlist_name = request_json['name']
|
||||
|
||||
return 404
|
||||
|
||||
else:
|
||||
error = {'error': 'not logged in'}
|
||||
return jsonify(error), 401
|
||||
|
||||
|
||||
@blueprint.route('/user', methods=['GET'])
|
||||
@ -55,13 +103,59 @@ def user():
|
||||
'validated': doc['validated']
|
||||
}
|
||||
|
||||
return jsonify(response)
|
||||
return jsonify(response), 200
|
||||
|
||||
else:
|
||||
error = {'error': 'username not in session, not logged in?'}
|
||||
return jsonify(error), 404
|
||||
error = {'error': 'not logged in'}
|
||||
return jsonify(error), 401
|
||||
|
||||
|
||||
@blueprint.route('/user/password', methods=['POST'])
|
||||
def change_password():
|
||||
|
||||
request_json = request.get_json()
|
||||
|
||||
if 'username' in session:
|
||||
|
||||
if 'new_password' in request_json and 'current_password' in request_json:
|
||||
|
||||
if len(request_json['new_password']) == 0:
|
||||
response = {"error": 'zero length password'}
|
||||
return jsonify(response), 400
|
||||
|
||||
if len(request_json['new_password']) > 30:
|
||||
response = {"error": 'password too long'}
|
||||
return jsonify(response), 400
|
||||
|
||||
users = db.collection(u'spotify_users').where(u'username', u'==', session['username']).stream()
|
||||
users = [i for i in users]
|
||||
|
||||
if len(users) == 1:
|
||||
current_user = db.collection(u'spotify_users').document(u'{}'.format(users[0].id))
|
||||
else:
|
||||
error = {'error': 'multiple usernames?'}
|
||||
return jsonify(error), 500
|
||||
|
||||
if check_password_hash(current_user.get().to_dict()['password'], request_json['current_password']):
|
||||
|
||||
current_user.update({'password': generate_password_hash(request_json['new_password'])})
|
||||
|
||||
response = {"message": 'password changed', "status": "success"}
|
||||
return jsonify(response), 200
|
||||
|
||||
else:
|
||||
error = {'error': 'wrong password provided'}
|
||||
return jsonify(error), 403
|
||||
|
||||
else:
|
||||
error = {'error': 'malformed request, no old_password/new_password'}
|
||||
return jsonify(error), 400
|
||||
|
||||
else:
|
||||
error = {'error': 'not logged in'}
|
||||
return jsonify(error), 401
|
||||
|
||||
|
||||
@blueprint.route('/playlist', methods=['GET', 'PUT', 'POST'])
|
||||
def playlist():
|
||||
return 404
|
||||
return 401
|
||||
|
@ -21,7 +21,11 @@ def login():
|
||||
|
||||
users = [i for i in users]
|
||||
|
||||
if len(users) != 1:
|
||||
if len(users) == 0:
|
||||
flash('user not found')
|
||||
return redirect(url_for('index'))
|
||||
|
||||
if len(users) > 1:
|
||||
flash('multiple users found')
|
||||
return redirect(url_for('index'))
|
||||
|
||||
|
@ -69,9 +69,9 @@ def token():
|
||||
return redirect('/app')
|
||||
|
||||
|
||||
@app.route('/app')
|
||||
@app.route('/app/<path>')
|
||||
def app_route(path = None):
|
||||
@app.route('/app', defaults={'path': ''})
|
||||
@app.route('/app/<path:path>')
|
||||
def app_route(path):
|
||||
|
||||
if 'username' not in session:
|
||||
flash('please log in')
|
||||
|
13
src/js/Admin/Admin.js
Normal file
13
src/js/Admin/Admin.js
Normal file
@ -0,0 +1,13 @@
|
||||
import React, { Component } from "react";
|
||||
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
|
||||
const axios = require('axios');
|
||||
|
||||
class Admin extends Component {
|
||||
render(){
|
||||
return (
|
||||
<p>admin</p>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default Admin
|
7
src/js/Error/NotFound.js
Normal file
7
src/js/Error/NotFound.js
Normal file
@ -0,0 +1,7 @@
|
||||
import React from "react";
|
||||
|
||||
function NotFound (props) {
|
||||
return <p style={{textAlign: "center"}}>404: Path Not Found</p>;
|
||||
}
|
||||
|
||||
export default NotFound;
|
@ -5,10 +5,8 @@ class Index extends Component{
|
||||
|
||||
constructor(props){
|
||||
super(props);
|
||||
this.state = {
|
||||
|
||||
}
|
||||
this.pingPlaylists();
|
||||
this.state = {}
|
||||
// this.pingPlaylists();
|
||||
}
|
||||
|
||||
pingPlaylists(){
|
@ -6,8 +6,9 @@ class PlaylistView extends Component{
|
||||
|
||||
constructor(props){
|
||||
super(props);
|
||||
console.log(this.props);
|
||||
this.state = {
|
||||
name: props.name
|
||||
name: this.props.match.name
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,58 +2,21 @@ import React, { Component } from "react";
|
||||
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
|
||||
const axios = require('axios');
|
||||
|
||||
import PlaylistsView from "./PlaylistsView.js"
|
||||
|
||||
class Playlists extends Component {
|
||||
render(){
|
||||
return (
|
||||
<div>
|
||||
<ul className="navbar" style={{width: "100%"}}>
|
||||
<li><Link to={`${this.props.match.url}/add`}>add</Link></li>
|
||||
</ul>
|
||||
|
||||
<Route path={`${this.props.match.url}/`} component={PlaylistsView} />
|
||||
|
||||
constructor(props){
|
||||
super(props);
|
||||
this.state = {
|
||||
isLoading: true
|
||||
}
|
||||
this.getPlaylists();
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
getPlaylists(){
|
||||
var self = this;
|
||||
axios.get('/api/playlists')
|
||||
.then((response) => {
|
||||
self.setState({
|
||||
playlists: response.data.playlists,
|
||||
isLoading: false
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
const table = <div><Table playlists={this.state.playlists}/></div>;
|
||||
const loadingMessage = <p className="center-text">loading...</p>;
|
||||
|
||||
return this.state.isLoading ? loadingMessage : table;
|
||||
}
|
||||
}
|
||||
|
||||
function Table(props){
|
||||
return (
|
||||
<div>
|
||||
{ props.playlists.map((playlist) => <Row playlist={ playlist } key={ playlist.name }/>) }
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Row(props){
|
||||
return (
|
||||
<PlaylistLink playlist={props.playlist}/>
|
||||
);
|
||||
}
|
||||
|
||||
function PlaylistLink(props){
|
||||
return (
|
||||
<Link to={ getPlaylistLink(props.playlist.name) }>{ props.playlist.name }</Link>
|
||||
);
|
||||
}
|
||||
|
||||
function getPlaylistLink(playlistName){
|
||||
return '/app/playlist/' + playlistName;
|
||||
}
|
||||
|
||||
export default Playlists;
|
65
src/js/Playlist/PlaylistsView.js
Normal file
65
src/js/Playlist/PlaylistsView.js
Normal file
@ -0,0 +1,65 @@
|
||||
import React, { Component } from "react";
|
||||
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
|
||||
const axios = require('axios');
|
||||
|
||||
class PlaylistsView extends Component {
|
||||
|
||||
constructor(props){
|
||||
super(props);
|
||||
this.state = {
|
||||
isLoading: true
|
||||
}
|
||||
this.getPlaylists();
|
||||
}
|
||||
|
||||
getPlaylists(){
|
||||
var self = this;
|
||||
axios.get('/api/playlists')
|
||||
.then((response) => {
|
||||
self.setState({
|
||||
playlists: response.data.playlists,
|
||||
isLoading: false
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
|
||||
const table = <div><Table playlists={this.state.playlists}/></div>;
|
||||
const loadingMessage = <p className="center-text">loading...</p>;
|
||||
|
||||
return this.state.isLoading ? loadingMessage : table;
|
||||
}
|
||||
}
|
||||
|
||||
function Table(props){
|
||||
return (
|
||||
<table className="app-table max-width">
|
||||
<tbody>
|
||||
{ props.playlists.map((playlist) => <Row playlist={ playlist } key={ playlist.name }/>) }
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
}
|
||||
|
||||
function Row(props){
|
||||
return (
|
||||
<tr>
|
||||
<PlaylistLink playlist={props.playlist}/>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
|
||||
function PlaylistLink(props){
|
||||
return (
|
||||
<td>
|
||||
<Link to={ getPlaylistLink(props.playlist.name) } className="button full-width">{ props.playlist.name }</Link>
|
||||
</td>
|
||||
);
|
||||
}
|
||||
|
||||
function getPlaylistLink(playlistName){
|
||||
return '/app/playlist/' + playlistName;
|
||||
}
|
||||
|
||||
export default PlaylistsView;
|
@ -1,29 +1,71 @@
|
||||
import React, { Component } from "react";
|
||||
import { BrowserRouter as Router, Route, Link } from "react-router-dom";
|
||||
import { BrowserRouter as Router, Route, Link, Switch, Redirect} from "react-router-dom";
|
||||
|
||||
import Index from "./Index.js";
|
||||
import Index from "./Index/Index.js";
|
||||
import Playlists from "./Playlist/Playlists.js";
|
||||
import PlaylistView from "./Playlist/PlaylistView.js";
|
||||
import Settings from "./Settings.js";
|
||||
import Settings from "./Settings/Settings.js";
|
||||
import Admin from "./Admin/Admin.js";
|
||||
|
||||
import NotFound from "./Error/NotFound.js";
|
||||
|
||||
const axios = require('axios');
|
||||
|
||||
class PlaylistManager extends Component {
|
||||
|
||||
constructor(props){
|
||||
super(props);
|
||||
this.state = {
|
||||
type: null
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
this.getUserInfo();
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.userInfoCancelToken.cancel();
|
||||
}
|
||||
|
||||
getUserInfo(){
|
||||
this.userInfoCancelToken = axios.CancelToken.source();
|
||||
|
||||
var self = this;
|
||||
axios.get('/api/user', {
|
||||
cancelToken: this.userInfoCancelToken.token
|
||||
})
|
||||
.then((response) => {
|
||||
self.setState({
|
||||
type: response.data.type
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
render(){
|
||||
return (
|
||||
<Router>
|
||||
<div className="card pad-12">
|
||||
<ul className="sidebar pad-3">
|
||||
<li><Link to="/app">home</Link></li>
|
||||
<li><Link to="/app/playlists">playlists</Link></li>
|
||||
<li><Link to="/app/settings">settings</Link></li>
|
||||
<li><a href="/auth/logout">logout</a></li>
|
||||
<li><a href="https://sarsoo.xyz">sarsoo.xyz</a></li>
|
||||
</ul>
|
||||
<table className="sidebar pad-3">
|
||||
<tbody>
|
||||
<tr><td><span><Link to="/app">home</Link></span></td></tr>
|
||||
<tr><td><Link to="/app/playlists">playlists</Link></td></tr>
|
||||
<tr><td><Link to="/app/settings">settings</Link></td></tr>
|
||||
{ this.state.type == 'admin' && <tr><td><Link to="/app/admin">admin</Link></td></tr> }
|
||||
<tr><td><a href="/auth/logout">logout</a></td></tr>
|
||||
<tr><td><a href="https://sarsoo.xyz">sarsoo.xyz</a></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div className="pad-9">
|
||||
<Route path="/app" exact component={Index} />
|
||||
<Route path="/app/playlists" exact component={Playlists} />
|
||||
<Route path="/app/settings" component={Settings} />
|
||||
<Switch>
|
||||
<Route path="/app" exact component={Index} />
|
||||
<Route path="/app/playlists" exact component={Playlists} />
|
||||
<Route path="/app/settings" component={Settings} />
|
||||
{ this.state.type == 'admin' && <Route path="/app/admin" component={Admin} /> }
|
||||
<Route path='/app/playlist/:name' component={PlaylistView} />
|
||||
<Route component={NotFound} />
|
||||
</Switch>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -1,8 +0,0 @@
|
||||
import React, { Component } from "react";
|
||||
|
||||
function Settings(props){
|
||||
|
||||
return <p>settings</p>;
|
||||
}
|
||||
|
||||
export default Settings;
|
126
src/js/Settings/ChangePassword.js
Normal file
126
src/js/Settings/ChangePassword.js
Normal file
@ -0,0 +1,126 @@
|
||||
import React, { Component } from "react";
|
||||
const axios = require('axios');
|
||||
|
||||
class ChangePassword extends Component {
|
||||
|
||||
constructor(props){
|
||||
super(props);
|
||||
this.state = {
|
||||
current: "",
|
||||
new1: "",
|
||||
new2: "",
|
||||
error: false,
|
||||
errorValue: null
|
||||
}
|
||||
this.handleCurrentChange = this.handleCurrentChange.bind(this);
|
||||
this.handleNewChange = this.handleNewChange.bind(this);
|
||||
this.handleNew2Change = this.handleNew2Change.bind(this);
|
||||
this.handleSubmit = this.handleSubmit.bind(this);
|
||||
}
|
||||
|
||||
handleCurrentChange(event){
|
||||
this.setState({
|
||||
'current': event.target.value
|
||||
});
|
||||
}
|
||||
handleNewChange(event){
|
||||
this.setState({
|
||||
'new1': event.target.value
|
||||
});
|
||||
}
|
||||
handleNew2Change(event){
|
||||
this.setState({
|
||||
'new2': event.target.value
|
||||
});
|
||||
}
|
||||
|
||||
handleSubmit(event){
|
||||
|
||||
if(this.state.current.length == 0){
|
||||
this.setState({
|
||||
error: true,
|
||||
errorValue: "enter current password"
|
||||
});
|
||||
}else{
|
||||
if(this.state.new1.length == 0){
|
||||
this.setState({
|
||||
error: true,
|
||||
errorValue: "enter new password"
|
||||
});
|
||||
}else{
|
||||
if(this.state.new1 != this.state.new2){
|
||||
this.setState({
|
||||
error: true,
|
||||
errorValue: "new password mismatch"
|
||||
});
|
||||
}else{
|
||||
|
||||
axios.post('/api/user/password',{
|
||||
current_password: this.state.current,
|
||||
new_password: this.state.new1
|
||||
}).then((response) => {
|
||||
this.setState({
|
||||
error: true,
|
||||
errorValue: "password changed"
|
||||
});
|
||||
}).catch((error) => {
|
||||
this.setState({
|
||||
error: true,
|
||||
errorValue: "error changing password"
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
|
||||
}
|
||||
|
||||
render(){
|
||||
return (
|
||||
<div>
|
||||
<h1>change password</h1>
|
||||
<form onSubmit={this.handleSubmit}>
|
||||
<table className="app-table max-width">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td className="ui-text center-text">current:</td>
|
||||
<td><input
|
||||
type="password"
|
||||
name="current"
|
||||
value={this.state.current}
|
||||
onChange={this.handleCurrentChange}
|
||||
className="full-width" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="ui-text center-text">new:</td>
|
||||
<td><input
|
||||
type="password"
|
||||
name="new1"
|
||||
value={this.state.new1}
|
||||
onChange={this.handleNewChange}
|
||||
className="full-width" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td className="ui-text center-text">new again:</td>
|
||||
<td><input
|
||||
type="password"
|
||||
name="new2"
|
||||
value={this.state.new2}
|
||||
onChange={this.handleNew2Change}
|
||||
className="full-width" /></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td colSpan="2"><input type="submit" style={{width: "100%"}} className="button" value="change" /></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</form>
|
||||
{ this.state.error && <p style={{color: "red"}}>{this.state.errorValue}</p>}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default ChangePassword;
|
24
src/js/Settings/Settings.js
Normal file
24
src/js/Settings/Settings.js
Normal file
@ -0,0 +1,24 @@
|
||||
import React, { Component } from "react";
|
||||
import { BrowserRouter as Router, Route, Link, Switch, Redirect} from "react-router-dom";
|
||||
|
||||
import ChangePassword from "./ChangePassword.js"
|
||||
|
||||
class Settings extends Component {
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<ul className="navbar" style={{width: "100%"}}>
|
||||
<li><Link to={`${this.props.match.url}/password`}>password</Link></li>
|
||||
</ul>
|
||||
|
||||
<Route path={`${this.props.match.url}/password`} component={ChangePassword} />
|
||||
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
export default Settings;
|
@ -1,6 +1,7 @@
|
||||
$font-stack: 'Lato', arial;
|
||||
$background-colour: #202124;
|
||||
$ui-colour: #131313;
|
||||
$light-ui: #575757;
|
||||
$text-colour: white;
|
||||
|
||||
$pad-px: 20px;
|
||||
@ -30,6 +31,10 @@ p {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.ui-text {
|
||||
color: $text-colour;
|
||||
}
|
||||
|
||||
.center-text {
|
||||
text-align: center;
|
||||
}
|
||||
@ -40,34 +45,17 @@ p {
|
||||
|
||||
.button {
|
||||
background-color: #505050;
|
||||
color: white;
|
||||
border-radius: 10px;
|
||||
display: inline-block;
|
||||
margin: 4px auto;
|
||||
cursor: pointer;
|
||||
padding: 15px;
|
||||
box-shadow: 2px 2px 4px black;
|
||||
/*-webkit-transition-duration: 0.4s;
|
||||
transition-duration: 0.4s;*/
|
||||
|
||||
text: {
|
||||
align: center;
|
||||
decoration: none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 12px 16px 0 rgba(0,0,0,0.24),0 17px 50px 0 rgba(0,0,0,0.19);
|
||||
}
|
||||
}
|
||||
color: $text-colour;
|
||||
|
||||
button {
|
||||
background-color: #505050;
|
||||
color: white;
|
||||
border-radius: 10px;
|
||||
display: inline-block;
|
||||
|
||||
border-radius: 10px;
|
||||
border: none;
|
||||
margin: 4px auto;
|
||||
cursor: pointer;
|
||||
padding: 15px;
|
||||
|
||||
cursor: pointer;
|
||||
|
||||
box-shadow: 2px 2px 4px black;
|
||||
/*-webkit-transition-duration: 0.4s;
|
||||
transition-duration: 0.4s;*/
|
||||
@ -127,6 +115,12 @@ h1.title {
|
||||
font-size: 6em;
|
||||
font-family: 'Pirata One', arial;
|
||||
text-shadow: 3px 3px 3px #aaa;
|
||||
|
||||
cursor: default;
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
|
||||
font-weight: bold;
|
||||
display: block;
|
||||
@ -152,7 +146,7 @@ h1.title {
|
||||
|
||||
h1 {
|
||||
text-align: center;
|
||||
color: white;
|
||||
color: $text-colour;
|
||||
text-shadow: 1px 1px 2px #4f4f4f;
|
||||
}
|
||||
|
||||
@ -162,7 +156,7 @@ h1.title {
|
||||
margin-bottom: 8px;
|
||||
box-shadow: 2px 2px 2px black;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
p {
|
||||
@ -172,7 +166,7 @@ h1.title {
|
||||
|
||||
h1.sectiontitle {
|
||||
text-align:center;
|
||||
color: white;
|
||||
color: $text-colour;
|
||||
font-family: 'Megrim', sans-serif;
|
||||
}
|
||||
|
||||
@ -183,8 +177,7 @@ ul.navbar {
|
||||
margin: 10px;
|
||||
padding: 0;
|
||||
overflow: hidden;
|
||||
background-color: $ui-colour;
|
||||
|
||||
background-color: $light-ui;
|
||||
|
||||
li {
|
||||
float: left;
|
||||
@ -195,9 +188,10 @@ ul.navbar {
|
||||
|
||||
a {
|
||||
display: block;
|
||||
color: white;
|
||||
color: $text-colour;
|
||||
text-align: center;
|
||||
padding: 14px 16px;
|
||||
|
||||
text-decoration: none;
|
||||
text-shadow: 1px 1px 2px black;
|
||||
-webkit-transition: background-color 0.4s;
|
||||
@ -214,31 +208,77 @@ ul.navbar {
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
li {
|
||||
// float: left;
|
||||
position: -webkit-sticky;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
height: 50px;
|
||||
|
||||
|
||||
background-color: $light-ui;
|
||||
list-style-type: none;
|
||||
margin-left: 10px;
|
||||
margin-right: 10px;
|
||||
border-radius: 5px;
|
||||
padding-top: 10px;
|
||||
padding-bottom: 10px;
|
||||
|
||||
a {
|
||||
// display: block;
|
||||
color: white;
|
||||
text-align: center;
|
||||
padding: 14px 16px;
|
||||
tr {
|
||||
// float: left;
|
||||
// position: -webkit-sticky;
|
||||
// position: sticky;
|
||||
top: 0;
|
||||
cursor: pointer;
|
||||
// padding: 10px;
|
||||
|
||||
td {
|
||||
|
||||
text-decoration: none;
|
||||
text-shadow: 1px 1px 2px black;
|
||||
-webkit-transition: background-color 0.4s;
|
||||
transition: background-color 0.4s;
|
||||
|
||||
height: 50px;
|
||||
text-align: center;
|
||||
vertical-align: center;
|
||||
|
||||
border-radius: 5px;
|
||||
|
||||
// margin-top: 10px;
|
||||
// margin-bottom: 10px;
|
||||
// margin: auto;
|
||||
|
||||
a {
|
||||
// display: block;
|
||||
color: $text-colour;
|
||||
// text-align: center;
|
||||
// vertical-align: center;
|
||||
// margin: 25px;
|
||||
// padding: 14px 16px;
|
||||
// border-radius: 5px;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
display: inline-block;
|
||||
vertical-align: center;
|
||||
padding: 10px;
|
||||
|
||||
text-decoration: none;
|
||||
text-shadow: 1px 1px 2px black;
|
||||
-webkit-transition: background-color 0.4s;
|
||||
transition: background-color 0.4s;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
background-color: #080808;
|
||||
background-color: black;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.sidebar-selected {
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
.app-table {
|
||||
width: 100%;
|
||||
margin: auto;
|
||||
td {
|
||||
padding: $pad-px;
|
||||
}
|
||||
}
|
||||
|
||||
.max-width {
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
footer {
|
||||
@ -257,7 +297,7 @@ footer {
|
||||
}
|
||||
}
|
||||
|
||||
@media only screen and (max-width: 600px) {
|
||||
@media only screen and (max-width: 768px) {
|
||||
ul.navbar li.right,
|
||||
ul.navbar li {float: none;}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user