Kaballas's picture
initialize project structure with essential configurations and components
56b6519
/* eslint-disable import/extensions */
import { XMarkIcon } from '@heroicons/react/20/solid';
import { useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { toast } from 'sonner';
import { PasswordStrengthCard } from '@/components/card/PasswordStrengthCard';
import { PasswordInput } from '@/components/input/PasswordInput';
import { ArrowPathIcon } from '@heroicons/react/24/outline';
import clsx from 'clsx';
type EncryptionModalProps = {
isOpen: boolean;
onCancel: () => void;
handleSubmitEncrypt: (password: string) => void;
auditName: string;
isGeneratingPDF: boolean;
};
export const EncryptionModal: React.FC<EncryptionModalProps> = ({
auditName,
isOpen,
isGeneratingPDF,
onCancel,
handleSubmitEncrypt,
}) => {
const { t } = useTranslation();
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
useEffect(() => {
if (isOpen) {
document.body.style.overflow = 'hidden';
} else {
document.body.style.overflow = 'unset';
}
}, [isOpen]);
const generatePassword = () => {
const charset =
// eslint-disable-next-line no-secrets/no-secrets
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()';
const passwordLength = 32;
let newPassword = '';
const randomValues = new Uint32Array(passwordLength);
window.crypto.getRandomValues(randomValues);
for (let i = 0; i < passwordLength; i++) {
if (randomValues[i] % 7 === 0) {
newPassword += '!@#$%^&*()'.charAt(randomValues[i] % 10);
} else if (randomValues[i] % 3 === 0) {
newPassword += '0123456789'.charAt(randomValues[i] % 10);
} else {
newPassword += charset.charAt(randomValues[i] % charset.length);
}
}
return newPassword;
};
const handlePasswordGen = () => {
const generated = generatePassword();
setPassword(generated);
setConfirmPassword(generated);
void navigator.clipboard.writeText(generated);
toast.info(t('msg.copiedToClipboard'));
};
useEffect(() => {
setPassword('');
setConfirmPassword('');
}, [onCancel]);
return (
isOpen && (
<div className="fixed z-50 inset-0 flex items-center justify-center bg-stone-900 bg-opacity-50">
<div className="bg-gray-900 rounded-lg shadow-lg p-6 w-full max-w-lg">
<div className="ml-3 mt-3 flex justify-between items-center mb-2">
<h2 className="text-xl font-bold text-center text-gray-200">
{`${t('encryption')} (${auditName})`}
</h2>
<button
className="bg-transparent text-white p-2 rounded mx-3"
onClick={onCancel}
type="button"
>
<XMarkIcon className="h-6 w-6" />
</button>
</div>
<hr className="h-1 mb-3 bg-gray-600 border-0 rounded" />
<div className="mb-2 text-gray-200 py-4">
<div className="flex flex-col">
<PasswordInput
disabled={isGeneratingPDF}
id="password"
label={t('password')}
name="password"
onChange={setPassword}
placeholder={t('password')}
value={password}
/>
<div className="py-2 pr-9">
<PasswordStrengthCard password={password} />
</div>
<PasswordInput
disabled={isGeneratingPDF}
id="confirmPassword"
label={t('confirmPassword')}
name="confirmPassword"
onChange={setConfirmPassword}
placeholder={t('confirmPassword')}
value={confirmPassword}
/>
</div>
<div
className={
password === confirmPassword || confirmPassword === ''
? 'flex items-center justify-center text-red-500/75 pt-2 invisible'
: 'flex items-center justify-center text-red-500/75 pt-2'
}
>
<p aria-live="polite">{t('err.passwordsDontMatch')}</p>
</div>
</div>
<hr className="h-1 mb-3 bg-gray-600 border-0 rounded" />
<div className="flex justify-end">
<button
className="bg-gray-500 text-white px-4 py-2 rounded mr-2"
onClick={onCancel}
type="button"
>
{t('btn.cancel')}
</button>
<button
className={clsx('text-white px-4 py-2 rounded mr-2', {
'bg-green-500': !isGeneratingPDF,
'bg-green-500/70': isGeneratingPDF,
})}
disabled={isGeneratingPDF}
onClick={handlePasswordGen}
type="button"
>
{t('btn.generatePassword')}
</button>
{!isGeneratingPDF ? (
<button
className={
!(password === confirmPassword) ||
password.length < 1 ||
confirmPassword.length < 1
? 'bg-blue-500/50 text-white/50 px-4 py-2 rounded'
: 'bg-blue-500 text-white px-4 py-2 rounded'
}
disabled={
!(password === confirmPassword) ||
password.length < 1 ||
confirmPassword.length < 1
}
onClick={() => handleSubmitEncrypt(password)}
type="button"
>
{t('btn.submitEncryption')}
</button>
) : (
<ArrowPathIcon className="h-8 w-8 animate-spin text-blue-500" />
)}
</div>
</div>
</div>
)
);
};