Skip to content

Commit

Permalink
Improve admin page styling
Browse files Browse the repository at this point in the history
  • Loading branch information
danielnhoward committed Oct 15, 2023
1 parent 3566a4c commit d2e93ac
Show file tree
Hide file tree
Showing 12 changed files with 220 additions and 46 deletions.
54 changes: 54 additions & 0 deletions public/admin/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Chaos Game Visualiser Admin</title>

<link rel="stylesheet" href="https://static.danielhoward.me/lib/bootstrap-5.0.2.min.css">
<link rel="stylesheet" href="https://static.danielhoward.me/lib/bootstrap-icons-1.11.0.min.css">
</head>
<body>
<div id="loading" class="center">
<div class="spinner-border" role="status" style="height: 15rem; width: 15rem;">
<span class="visually-hidden">Loading...</span>
</div>
</div>
<div id="login" class="center hidden">
<button id="loginButton" type="button" class="btn btn-primary"><i class="bi bi-cloud-fill"></i> Log in</button>
</div>
<div id="errorAlert" class="center hidden">
<div role="alert" class="alert alert-danger">
<h4 class="alert-heading">An error has occured</h4>
An error has occured whilst loading the page. Please try again later.
</div>
</div>
<div id="unauthorisedAlert" class="center hidden">
<div role="alert" class="alert alert-warning">
<h4 class="alert-heading">Unauthorised</h4>
You need to login with an admin account in order to view this page.
</div>
</div>
<div id="page" class="hidden">
<div class="settings-container">
<div class="container">
<div class="row">
<div class="col-12 col-lg-8">
<fieldset>
<legend>Presets</legend>
</fieldset>
</div>
<div class="col-12 col-lg-4">
<fieldset>
<legend>Screenshots</legend>
</fieldset>
<fieldset>
<legend>Admins</legend>
</fieldset>
</div>
</div>
</div>
</div>
</div>
</body>
</html>
57 changes: 57 additions & 0 deletions src/admin/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import './../styles/admin.css';

import {fetchUserSaves} from './../lib/backend';
import {hasAuthInStorage, openLoginPopup} from './../lib/sso';

function $<T extends HTMLElement = HTMLElement>(id: string): T {
return <T> document.getElementById(id);
}

const errorAlert = $('errorAlert');
const unauthorisedAlert = $('unauthorisedAlert');

const loading = $('loading');

const login = $('login');
const loginButton = $('loginButton');

const page = $('page');

function showView(show: HTMLElement) {
([
errorAlert,
unauthorisedAlert,
loading,
login,
page,
]).forEach((c) => c.classList.toggle('hidden', c !== show));
}

async function init() {
const {account} = await fetchUserSaves();

if (!account.admin) {
showView(unauthorisedAlert);
return;
}

showView(page);
}

function onload() {
if (hasAuthInStorage()) {
init();
} else {
loginButton.addEventListener('click', async () => {
const loggedIn = await openLoginPopup();
if (loggedIn) {
showView(loading);
init();
}
});

showView(login);
}
}

window.addEventListener('DOMContentLoaded', onload);
File renamed without changes.
File renamed without changes.
49 changes: 49 additions & 0 deletions src/lib/sso.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import {ssoPath} from './paths';

import type {LocalStorageAuth} from './../types.d';

export function getAuthStorage(): LocalStorageAuth {
const auth = JSON.parse(localStorage.getItem('auth')) as LocalStorageAuth;
if (!auth) throw new Error('No auth in storage');
if (auth.expires < Date.now()) throw new Error('accessToken expired');
return auth;
}

export function hasAuthInStorage(): boolean {
try {
getAuthStorage();
return true;
} catch (_) {
return false;
}
}

let loginWindow: Window;
export function openLoginPopup(): Promise<boolean> {
return new Promise((res) => {
if (loginWindow?.closed === false) loginWindow.focus();
else loginWindow = window.open(ssoPath, '', 'width=500, height=600');

const interval = setInterval(() => {
// Test if the page has been closed
if (loginWindow.closed) {
clearInterval(interval);
res(false);
return;
}

// Test if the origin has changed, meaning it has been authed
let originsMatch = false;
try {
originsMatch = window.location.origin === loginWindow.location.origin;
} catch (err) {
// Ignore error thrown by browser since it is expected when on a different origin
}
if (originsMatch && localStorage.getItem('auth') !== null) {
clearInterval(interval);
loginWindow.close();
res(true);
}
}, 1000);
});
}
2 changes: 1 addition & 1 deletion src/saves/local.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {$, makeClassToggler} from './../core';
import {requestScreenshot} from './backend';
import {requestScreenshot} from './../lib/backend';
import {getCurrentConfig} from './config';
import {SaveType, addSaveToSection, populateSavesSection} from './selector';

Expand Down
2 changes: 1 addition & 1 deletion src/saves/presets.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import {$, makeClassToggler} from './../core';
import {fetchPresets} from './backend';
import {fetchPresets} from './../lib/backend';
import {SaveType, populateSavesSection} from './selector';

import type {Save} from './../types.d';
Expand Down
2 changes: 1 addition & 1 deletion src/saves/save.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {SetupStage} from './../constants';
import {$, makeClassToggler} from './../core';
import {makeCloudSave, requestScreenshot} from './../lib/backend';
import {getSetupStage} from './../setup/setup';
import {makeCloudSave, requestScreenshot} from './backend';
import {getCurrentConfig} from './config';
import {createLocalSave, downloadConfig} from './local';
import {SaveType, addSaveToSection} from './selector';
Expand Down
4 changes: 2 additions & 2 deletions src/saves/selector.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import {SCREENSHOT_GENEREATION_CHECK_INTERVAL, ScreenshotStatus} from './../constants';
import {$, makeClassToggler} from './../core';
import {getScreenshotStatus, requestScreenshot} from './backend';
import {getScreenshotStatus, requestScreenshot} from './../lib/backend';
import {backendOrigin} from './../lib/paths';
import {loadConfig} from './config';
import {backendOrigin} from './paths';

import type {Save, SaveConfig} from './../types.d';

Expand Down
55 changes: 14 additions & 41 deletions src/saves/sso.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import {$, makeClassToggler} from './../core';
import {fetchUserSaves, deleteSave} from './backend';
import {backendOrigin, ssoOrigin, ssoPath} from './paths';
import {fetchUserSaves, deleteSave} from './../lib/backend';
import {ssoOrigin} from './../lib/paths';
import {hasAuthInStorage, openLoginPopup} from './../lib/sso';
import {SaveType, populateSavesSection} from './selector';

import type {Account, BackendResponse, LocalStorageAuth} from './../types.d';
import type {Account, BackendResponse} from './../types.d';

const loginButton = $<HTMLButtonElement>('loginButton');
const logoutButton = $('logoutButton');
Expand All @@ -25,39 +26,19 @@ const setLoginError = (value: string | undefined) => {
showLoginError(!!value);
};

let loginWindow: Window;

const statusChangeCallbacks: ((loggedIn: boolean) => void)[] = [];

function onLoginClick() {
if (loginWindow?.closed === false) return loginWindow.focus();

async function onLoginClick() {
setLoginError(null);
showLoadingInfo(true);
loginLoadingText.textContent = 'Waiting for authentication in popup window';
loginWindow = window.open(ssoPath, '', 'width=500, height=600');

const interval = setInterval(() => {
// Test if the page has been closed
if (loginWindow.closed) {
showLoadingInfo(false);
clearInterval(interval);
return;
}

// Test if the origin has changed, meaning it has been authed
let originsMatch = false;
try {
originsMatch = window.location.origin === loginWindow.location.origin;
} catch (err) {
// Ignore error thrown by browser since it is expected when on a different origin
}
if (originsMatch && localStorage.getItem('auth') !== null) {
clearInterval(interval);
loginWindow.close();
refreshServerResponse();
}
}, 1000);

const loggedIn = await openLoginPopup();
if (loggedIn) {
refreshServerResponse();
} else {
showLoadingInfo(false);
}
}

async function refreshServerResponse() {
Expand Down Expand Up @@ -94,6 +75,7 @@ function populateAccountDetails(account: Account) {

if (account.admin) {
const adminLink = document.createElement('a');
adminLink.href = `/admin${window.location.search}`;
adminLink.target = '_blank';
adminLink.classList.add('btn');
adminLink.classList.add('btn-outline-primary');
Expand All @@ -105,19 +87,10 @@ function populateAccountDetails(account: Account) {
gearIcon.classList.add('bi-gear');
adminLink.prepend(gearIcon);

const auth = getAuthStorage();
adminLink.href = `${backendOrigin}/admin#token=${auth.accessToken}`;

loggedInView.querySelector('#profileButtons').prepend(adminLink);
}
}

export function getAuthStorage(): LocalStorageAuth {
const auth = JSON.parse(localStorage.getItem('auth')) as LocalStorageAuth;
if (auth.expires < Date.now()) throw new Error('accessToken expired');
return auth;
}

function logout() {
localStorage.removeItem('auth');
showLoadingInfo(false);
Expand All @@ -142,7 +115,7 @@ export function onload() {
loginButton.addEventListener('click', onLoginClick);
logoutButton.addEventListener('click', logout);

if (localStorage.getItem('auth') !== null) {
if (hasAuthInStorage()) {
showLoginButton(false);
refreshServerResponse();
}
Expand Down
35 changes: 35 additions & 0 deletions src/styles/admin.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
.hidden {
display: none !important;
}

.center {
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}

body:has(#page:not(.hidden)) {
background: whitesmoke;
}

.settings-container {
margin: 20px;
}

@media (min-width: 992px) {
.settings-container {
height: calc(100vh - 40px);
}
}

.settings-container fieldset {
background: white;
border: lightgray 1px solid;
border-radius: 5px;
padding: 10px;
}
.settings-container legend {
float: initial;
width: auto;
}
6 changes: 6 additions & 0 deletions webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export default {
entry: {
main: localPath('src/index.ts'),
auth: localPath('src/auth/index.ts'),
admin: localPath('src/admin/index.ts'),
},
devtool: (isProduction || isStaging) ? false : 'eval-source-map',
module: {
Expand Down Expand Up @@ -66,6 +67,11 @@ export default {
filename: 'auth/index.html',
chunks: ['auth'],
}),
new HtmlWebpackPlugin({
template: localPath('public/admin/index.html'),
filename: 'admin/index.html',
chunks: ['admin'],
}),
],
resolve: {
extensions: ['.ts', '.js'],
Expand Down

0 comments on commit d2e93ac

Please sign in to comment.