mirror of
https://github.com/quantum5/totp.git
synced 2025-04-24 13:41:58 -04:00
Add basic styling
This commit is contained in:
parent
d8636039a9
commit
4f87a59307
30
index.html
30
index.html
|
@ -4,10 +4,38 @@
|
||||||
<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>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
<script type="module" src="/src/main.tsx"></script>
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
@ -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(() => {
|
||||||
|
|
|
@ -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>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in a new issue