Add basic styling

This commit is contained in:
Quantum 2024-04-07 01:04:16 -04:00
parent d8636039a9
commit 4f87a59307
5 changed files with 113 additions and 15 deletions

View file

@ -1,13 +1,41 @@
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8"/>
<link rel="icon" type="image/svg+xml" href="/vite.svg" /> <link rel="icon" type="image/svg+xml" href="/vite.svg"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>Vite + React + TS</title> <title>TOTP.lol — A TOTP Code Generator for Developers</title>
</head> </head>
<body> <body>
<nav class="navbar navbar-expand-lg">
<div class="container">
<a class="navbar-brand" href="#">TOTP.lol</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent"
aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link" href="#what-is-this">What is this?</a>
</li>
<li class="nav-item">
<a class="nav-link" href="#about">About</a>
</li>
</ul>
</div>
</div>
</nav>
<main class="container">
<noscript>You need to enable JavaScript to run this app.</noscript>
<div class="jumbotron">
<p class="lead">This is a code generator for the Time-based One Time Password (TOTP) algorithm.</p>
<hr>
<div id="root"></div> <div id="root"></div>
<script type="module" src="/src/main.tsx"></script> </div>
</body> </main>
<script type="module" src="/src/main.tsx"></script>
</body>
</html> </html>

View file

@ -11,7 +11,7 @@ function App() {
const [algorithm, setAlgorithm] = React.useState<HashAlgorithm>('sha1'); const [algorithm, setAlgorithm] = React.useState<HashAlgorithm>('sha1');
const validStep = step > 0; const validStep = step > 0;
const validDigits = digits > 0 && digits < 10; const validDigits = digits > 0 && digits <= 10;
const valid = validStep && validDigits; const valid = validStep && validDigits;
React.useEffect(() => { React.useEffect(() => {

View file

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import {HashAlgorithms, HOTP, HOTPOptions} from '@otplib/core'; import {HashAlgorithms, HOTP, HOTPOptions} from '@otplib/core';
import {createDigest} from '@otplib/plugin-crypto-js'; import {createDigest} from '@otplib/plugin-crypto-js';
import classNames from 'classnames';
const ALGORITHMS = { const ALGORITHMS = {
sha1: HashAlgorithms.SHA1, sha1: HashAlgorithms.SHA1,
@ -10,8 +11,13 @@ const ALGORITHMS = {
export type HashAlgorithm = keyof typeof ALGORITHMS; export type HashAlgorithm = keyof typeof ALGORITHMS;
function OTPCode({code}: { code: string }) { function OTPCode({code, delta}: { code: string; delta: number }) {
return <div className="totp-code"> return <div className={classNames('totp-code', {
'totp-older': delta < 0,
'totp-newer': delta > 0,
'totp-current': delta === 0,
'totp-far': Math.abs(delta) > 5,
})}>
{code} {code}
</div>; </div>;
} }
@ -22,7 +28,7 @@ export default function OTPOutput({secret, offset, algorithm, digits}: {
algorithm: HashAlgorithm; algorithm: HashAlgorithm;
digits: number; digits: number;
}) { }) {
const hotp = React.useMemo(() => new HOTP<HOTPOptions>({ const otp = React.useMemo(() => new HOTP<HOTPOptions>({
createDigest, createDigest,
digits, digits,
algorithm: ALGORITHMS[algorithm], algorithm: ALGORITHMS[algorithm],
@ -30,8 +36,9 @@ export default function OTPOutput({secret, offset, algorithm, digits}: {
return <div className="totp-output"> return <div className="totp-output">
{[...Array(21).keys()].map((i) => { {[...Array(21).keys()].map((i) => {
const current = offset + i - 10; const delta = i - 10;
return <OTPCode key={current} code={hotp.generate(secret, current)}/>; const current = offset + delta;
return <OTPCode key={current} code={otp.generate(secret, current)} delta={delta}/>;
})} })}
</div>; </div>;
} }

View file

@ -1,14 +1,76 @@
@import 'bootstrap/scss/_functions.scss'; @import 'bootstrap/scss/_functions.scss';
@import 'bootstrap/scss/_variables.scss'; @import 'bootstrap/scss/_variables.scss';
@import 'bootstrap/scss/_variables-dark.scss';
@import 'bootstrap/scss/_maps.scss'; @import 'bootstrap/scss/_maps.scss';
@import 'bootstrap/scss/_mixins.scss'; @import 'bootstrap/scss/_mixins.scss';
@import 'bootstrap/scss/_utilities.scss';
@import 'bootstrap/scss/_root.scss'; @import 'bootstrap/scss/_root.scss';
@import 'bootstrap/scss/_reboot.scss';
@import 'bootstrap/scss/_grid.scss'; @import 'bootstrap/scss/_grid.scss';
@import 'bootstrap/scss/_containers.scss';
@import 'bootstrap/scss/_navbar.scss';
@import 'bootstrap/scss/_nav.scss';
@import 'bootstrap/scss/_dropdown.scss';
@import 'bootstrap/scss/_buttons.scss'; @import 'bootstrap/scss/_buttons.scss';
@import 'bootstrap/scss/_forms.scss'; @import 'bootstrap/scss/_forms.scss';
@import 'bootstrap/scss/_type.scss';
@import 'bootstrap/scss/_helpers.scss';
@import 'bootstrap/scss/_transitions.scss';
nav {
background-color: $body-tertiary-bg;
}
.jumbotron {
margin: 1em 0;
border-radius: 1em;
padding: 1em;
background-color: $body-secondary-bg;
}
@include media-breakpoint-up(lg) {
.jumbotron {
padding: 1.5em;
}
}
@include media-breakpoint-up(sm) {
.totp-app {
@include make-row();
}
.totp-settings {
@include make-col-ready();
@include make-col($size: 1, $columns: 2);
}
.totp-output {
@include make-col-ready();
@include make-col($size: 1, $columns: 2);
}
}
.totp-input, .totp-select { .totp-input, .totp-select {
label { label {
font-weight: $font-weight-bold; font-weight: $font-weight-bold;
} }
margin-bottom: 0.5em;
}
.totp-code {
text-align: center;
height: 1.5em;
max-height: 1.5em;
transition: transform 0.5s linear, max-height 0.5s linear, font-size 0.5s linear;
&:first-child, &:last-child {
max-height: 0;
transform: scale(1, 0);
}
&.totp-current {
font-size: 2em;
}
} }

View file

@ -2,6 +2,7 @@ import React from 'react';
import ReactDOM from 'react-dom/client'; import ReactDOM from 'react-dom/client';
import App from './App.tsx'; import App from './App.tsx';
import './index.scss'; import './index.scss';
import 'bootstrap/js/src/collapse.js';
ReactDOM.createRoot(document.getElementById('root')!).render( ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode> <React.StrictMode>