feat(preferences): 2FA activation dialog with mocked state (#605)
This commit is contained in:
28
app/assets/javascripts/components/Button.tsx
Normal file
28
app/assets/javascripts/components/Button.tsx
Normal file
@@ -0,0 +1,28 @@
|
||||
import { FunctionComponent } from 'preact';
|
||||
|
||||
const baseClass = `rounded px-4 py-1.75 font-bold text-sm fit-content cursor-pointer`;
|
||||
|
||||
const normalClass = `${baseClass} bg-default color-text border-solid border-gray-300 border-1 \
|
||||
focus:bg-contrast hover:bg-contrast`;
|
||||
const primaryClass = `${baseClass} no-border bg-info color-info-contrast hover:brightness-130 \
|
||||
focus:brightness-130`;
|
||||
|
||||
export const Button: FunctionComponent<{
|
||||
className?: string;
|
||||
type: 'normal' | 'primary';
|
||||
label: string;
|
||||
onClick: () => void;
|
||||
}> = ({ type, label, className = '', onClick }) => {
|
||||
const buttonClass = type === 'primary' ? primaryClass : normalClass;
|
||||
return (
|
||||
<button
|
||||
className={`${buttonClass} ${className}`}
|
||||
onClick={(e) => {
|
||||
onClick();
|
||||
e.preventDefault();
|
||||
}}
|
||||
>
|
||||
{label}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
38
app/assets/javascripts/components/CircleProgress.tsx
Normal file
38
app/assets/javascripts/components/CircleProgress.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import { FunctionComponent } from 'preact';
|
||||
|
||||
export const CircleProgress: FunctionComponent<{
|
||||
percent: number;
|
||||
className?: string;
|
||||
}> = ({ percent, className = '' }) => {
|
||||
const size = 16;
|
||||
const ratioStrokeRadius = 0.25;
|
||||
const outerRadius = size / 2;
|
||||
|
||||
const radius = outerRadius * (1 - ratioStrokeRadius);
|
||||
const stroke = outerRadius - radius;
|
||||
|
||||
const circumference = radius * 2 * Math.PI;
|
||||
const offset = circumference - (percent / 100) * circumference;
|
||||
|
||||
const transition = `transition: 0.35s stroke-dashoffset;`;
|
||||
const transform = `transform: rotate(-90deg);`;
|
||||
const transformOrigin = `transform-origin: 50% 50%;`;
|
||||
const dasharray = `stroke-dasharray: ${circumference} ${circumference};`;
|
||||
const dashoffset = `stroke-dashoffset: ${offset};`;
|
||||
const style = `${transition} ${transform} ${transformOrigin} ${dasharray} ${dashoffset}`;
|
||||
return (
|
||||
<div className="h-5 w-5 min-w-5 min-h-5">
|
||||
<svg viewBox={`0 0 ${size} ${size}`}>
|
||||
<circle
|
||||
stroke="#086DD6"
|
||||
stroke-width={stroke}
|
||||
fill="transparent"
|
||||
r={radius}
|
||||
cx="50%"
|
||||
cy="50%"
|
||||
style={style}
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
27
app/assets/javascripts/components/CircleProgressTime.tsx
Normal file
27
app/assets/javascripts/components/CircleProgressTime.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { FunctionalComponent } from 'preact';
|
||||
import { useEffect, useState } from 'preact/hooks';
|
||||
import { CircleProgress } from './CircleProgress';
|
||||
|
||||
/**
|
||||
* Circular progress bar which runs in a specified time interval
|
||||
* @param time - time interval in ms
|
||||
*/
|
||||
export const CircleProgressTime: FunctionalComponent<{ time: number }> = ({
|
||||
time,
|
||||
}) => {
|
||||
const [percent, setPercent] = useState(0);
|
||||
const interval = time / 100;
|
||||
useEffect(() => {
|
||||
const tick = setInterval(() => {
|
||||
if (percent === 100) {
|
||||
setPercent(0);
|
||||
} else {
|
||||
setPercent(percent + 1);
|
||||
}
|
||||
}, interval);
|
||||
return () => {
|
||||
clearInterval(tick);
|
||||
};
|
||||
});
|
||||
return <CircleProgress percent={percent} />;
|
||||
};
|
||||
@@ -26,12 +26,12 @@ export const DecoratedInput: FunctionalComponent<Props> = ({
|
||||
const classes = `${base} ${stateClasses} ${className}`;
|
||||
|
||||
return (
|
||||
<div className={classes}>
|
||||
<div className={`${classes} focus-within:ring-info`}>
|
||||
{left}
|
||||
<div className="flex-grow">
|
||||
<input
|
||||
type="text"
|
||||
className="w-full no-border color-black"
|
||||
className="w-full no-border color-black focus:shadow-none"
|
||||
disabled={disabled}
|
||||
value={text}
|
||||
/>
|
||||
|
||||
@@ -25,6 +25,7 @@ import ThemesIcon from '../../icons/ic-themes.svg';
|
||||
import UserIcon from '../../icons/ic-user.svg';
|
||||
import CopyIcon from '../../icons/ic-copy.svg';
|
||||
import DownloadIcon from '../../icons/ic-download.svg';
|
||||
import InfoIcon from '../../icons/ic-info.svg';
|
||||
|
||||
import { toDirective } from './utils';
|
||||
import { FunctionalComponent } from 'preact';
|
||||
@@ -56,6 +57,7 @@ const ICONS = {
|
||||
user: UserIcon,
|
||||
copy: CopyIcon,
|
||||
download: DownloadIcon,
|
||||
info: InfoIcon,
|
||||
};
|
||||
|
||||
export type IconType = keyof typeof ICONS;
|
||||
|
||||
@@ -27,7 +27,7 @@ export const IconButton: FunctionComponent<Props> = ({
|
||||
};
|
||||
return (
|
||||
<button
|
||||
className={`no-border bg-transparent hover:brightness-130 p-0 ${
|
||||
className={`no-border cursor-pointer bg-transparent hover:brightness-130 p-0 ${
|
||||
className ?? ''
|
||||
}`}
|
||||
onClick={click}
|
||||
|
||||
Reference in New Issue
Block a user