From 85c87b4997413b7245e935e1c58d1498bc865e0f Mon Sep 17 00:00:00 2001 From: secondl1ght Date: Mon, 28 Oct 2024 21:29:23 -0600 Subject: [PATCH] refactor: settings redesign --- messages/en.json | 43 ++ messages/es.json | 43 ++ .../(layout)/settings/appearance/page.tsx | 5 + .../(app)/(layout)/settings/language/page.tsx | 5 + .../(app)/(layout)/settings/passkeys/page.tsx | 5 + .../(app)/(layout)/settings/password/page.tsx | 5 + src/components/button/LogoutButton.tsx | 2 + src/components/form/LoginForm.tsx | 7 + src/components/form/SignUpForm.tsx | 6 +- src/components/toggle/LanguageToggle.tsx | 53 +- src/components/toggle/ThemeToggle.tsx | 26 +- src/components/ui/form.tsx | 4 +- src/components/ui/progress.tsx | 10 +- src/utils/routes.ts | 4 + src/views/dashboard/Dashboard.tsx | 47 +- src/views/settings/Appearance.tsx | 33 ++ src/views/settings/ChangePassword.tsx | 451 ------------------ src/views/settings/Language.tsx | 31 ++ .../settings/{Passkey.tsx => Passkeys.tsx} | 210 ++++---- src/views/settings/Password.tsx | 413 ++++++++++++++++ src/views/settings/Section.tsx | 20 - src/views/settings/Setting.tsx | 44 ++ src/views/settings/Settings.tsx | 202 +++++++- src/views/settings/TwoFactor.tsx | 106 ++-- src/views/settings/TwoFactorOtp.tsx | 232 ++++----- src/views/settings/TwoFactorPasskey.tsx | 92 ++-- 26 files changed, 1276 insertions(+), 823 deletions(-) create mode 100644 src/app/(app)/(layout)/settings/appearance/page.tsx create mode 100644 src/app/(app)/(layout)/settings/language/page.tsx create mode 100644 src/app/(app)/(layout)/settings/passkeys/page.tsx create mode 100644 src/app/(app)/(layout)/settings/password/page.tsx create mode 100644 src/views/settings/Appearance.tsx delete mode 100644 src/views/settings/ChangePassword.tsx create mode 100644 src/views/settings/Language.tsx rename src/views/settings/{Passkey.tsx => Passkeys.tsx} (60%) create mode 100644 src/views/settings/Password.tsx delete mode 100644 src/views/settings/Section.tsx create mode 100644 src/views/settings/Setting.tsx diff --git a/messages/en.json b/messages/en.json index bb50f96f..6a7b2c13 100644 --- a/messages/en.json +++ b/messages/en.json @@ -3,10 +3,12 @@ "Dashboard": { "asset": "An asset in your wallet.", "buy": "Buy", + "go": "Go", "liquid-explainer": "Liquid Bitcoin (L-BTC) is a wrapped version of Bitcoin (BTC) issued by the Liquid Network, a sidechain-based, Bitcoin layer-2 settlement network. It is a 1:1 pegged asset, meaning that one L-BTC is equivalent to one BTC. L-BTC is designed to improve the functionality of Bitcoin transactions, issuance of digital assets, and exchanges.", "no-contacts": "No contacts to display.", "price": "Price", "recent": "Recent Contacts", + "secure": "Please secure your account with a stronger password.", "sell": "Sell", "tether-explainer": "Tether (USDT) is a stablecoin, a type of cryptocurrency designed to maintain a stable value relative to a fiat currency, in this case, the United States dollar (USD). It is issued by Tether Limited, a company founded in 2014, and is pegged to the value of the US dollar.", "transactions": "Recent Transactions", @@ -14,6 +16,47 @@ "warning-title": "Add Bitcoin!", "what-asset": "What is {asset}?" }, + "Settings": { + "2fa": "2-Factor Authentication", + "add-passkey": "Add Passkey", + "added": "Added", + "appearance": "Appearance", + "auth-app": "Authenticator App", + "change-pass": "Change Password", + "current-pass": "Current Password", + "dark": "Dark", + "dark-mode": "Dark Mode", + "display": "Display Mode", + "enable-encrypt": "Enable Encryption", + "enabled": "Enabled", + "encrypt-enabled": "Encryption Enabled", + "enter-otp": "Enter the one time password:", + "good": "Good", + "lang": "Preferred Language", + "language": "Language", + "light": "Light", + "light-mode": "Light Mode", + "login-only": "Login Only", + "login-wallet": "Login & Wallet", + "new-pass": "New Password", + "no-scan": "No QR code scanner? Enter the text below:", + "not-set": "Not Set", + "off": "OFF", + "otp": "Scan the QR code below with your preferred authenticator app or manually enter the code provided. Once set up, you will need to enter a one-time password (OTP) generated by the app each time you log in.", + "pass-confirm": "I have saved my password and understand that if I lose it I cannot recover my account and access to my funds will be lost forever.", + "passkeys": "Passkeys", + "saved-passkeys": "Saved Passkeys", + "setup": "Set Up", + "setup-2fa": "Setup 2FA", + "strong": "Strong", + "system": "System", + "system-mode": "System Mode", + "unknown-mode": "Unknown Mode", + "unlock-encrypt": "Unlock to Encrypt", + "very-strong": "Very Strong", + "very-weak": "Very Weak", + "weak": "Weak" + }, "Wallet": { "Settings": { "decrypt": "Decrypt to view your wallet’s secret mnemonic.", diff --git a/messages/es.json b/messages/es.json index 05adff7a..6dec180b 100644 --- a/messages/es.json +++ b/messages/es.json @@ -3,10 +3,12 @@ "Dashboard": { "asset": "Un activo en tu billetera.", "buy": "Comprar", + "go": "Go", "liquid-explainer": "Liquid Bitcoin (L-BTC) es una versión envuelta de Bitcoin (BTC) emitida por la Liquid Network, una red de liquidación de segunda capa basada en una cadena lateral de Bitcoin. Es un activo vinculado 1:1, lo que significa que un L-BTC es equivalente a un BTC. L-BTC está diseñado para mejorar la funcionalidad de las transacciones de Bitcoin, la emisión de activos digitales y los intercambios.", "no-contacts": "No hay contactos para mostrar.", "price": "Precio", "recent": "Contactos Recientes", + "secure": "Please secure your account with a stronger password.", "sell": "Vender", "tether-explainer": "Tether (USDT) es una stablecoin, un tipo de criptomoneda diseñada para mantener un valor estable en relación con una moneda fiduciaria, en este caso, el dólar estadounidense (USD). Es emitida por Tether Limited, una compañía fundada en 2014, y está vinculada al valor del dólar estadounidense.", "transactions": "Transacciones Recientes", @@ -14,6 +16,47 @@ "warning-title": "Agrega Bitcoin!", "what-asset": "Qué es {asset}?" }, + "Settings": { + "2fa": "2-Factor Authentication", + "add-passkey": "Add Passkey", + "added": "Added", + "appearance": "Appearance", + "auth-app": "Authenticator App", + "change-pass": "Change Password", + "current-pass": "Current Password", + "dark": "Dark", + "dark-mode": "Dark Mode", + "display": "Display Mode", + "enable-encrypt": "Enable Encryption", + "enabled": "Enabled", + "encrypt-enabled": "Encryption Enabled", + "enter-otp": "Enter the one time password:", + "good": "Good", + "lang": "Preferred Language", + "language": "Language", + "light": "Light", + "light-mode": "Light Mode", + "login-only": "Login Only", + "login-wallet": "Login & Wallet", + "new-pass": "New Password", + "no-scan": "No QR code scanner? Enter the text below:", + "not-set": "Not Set", + "off": "OFF", + "otp": "Scan the QR code below with your preferred authenticator app or manually enter the code provided. Once set up, you will need to enter a one-time password (OTP) generated by the app each time you log in.", + "pass-confirm": "I have saved my password and understand that if I lose it I cannot recover my account and access to my funds will be lost forever.", + "passkeys": "Passkeys", + "saved-passkeys": "Saved Passkeys", + "setup": "Set Up", + "setup-2fa": "Setup 2FA", + "strong": "Strong", + "system": "System", + "system-mode": "System Mode", + "unknown-mode": "Unknown Mode", + "unlock-encrypt": "Unlock to Encrypt", + "very-strong": "Very Strong", + "very-weak": "Very Weak", + "weak": "Weak" + }, "Wallet": { "Settings": { "decrypt": "Desencriptar para ver la frase secreta de tu billetera.", diff --git a/src/app/(app)/(layout)/settings/appearance/page.tsx b/src/app/(app)/(layout)/settings/appearance/page.tsx new file mode 100644 index 00000000..3cf51eb2 --- /dev/null +++ b/src/app/(app)/(layout)/settings/appearance/page.tsx @@ -0,0 +1,5 @@ +import { Appearance } from '@/views/settings/Appearance'; + +export default function Page() { + return ; +} diff --git a/src/app/(app)/(layout)/settings/language/page.tsx b/src/app/(app)/(layout)/settings/language/page.tsx new file mode 100644 index 00000000..70da639f --- /dev/null +++ b/src/app/(app)/(layout)/settings/language/page.tsx @@ -0,0 +1,5 @@ +import { Language } from '@/views/settings/Language'; + +export default function Page() { + return ; +} diff --git a/src/app/(app)/(layout)/settings/passkeys/page.tsx b/src/app/(app)/(layout)/settings/passkeys/page.tsx new file mode 100644 index 00000000..fc7d6ac6 --- /dev/null +++ b/src/app/(app)/(layout)/settings/passkeys/page.tsx @@ -0,0 +1,5 @@ +import { Passkeys } from '@/views/settings/Passkeys'; + +export default function Page() { + return ; +} diff --git a/src/app/(app)/(layout)/settings/password/page.tsx b/src/app/(app)/(layout)/settings/password/page.tsx new file mode 100644 index 00000000..76f17c7a --- /dev/null +++ b/src/app/(app)/(layout)/settings/password/page.tsx @@ -0,0 +1,5 @@ +import { Password } from '@/views/settings/Password'; + +export default function Page() { + return ; +} diff --git a/src/components/button/LogoutButton.tsx b/src/components/button/LogoutButton.tsx index e3411e93..18bfc2c1 100644 --- a/src/components/button/LogoutButton.tsx +++ b/src/components/button/LogoutButton.tsx @@ -23,6 +23,7 @@ export const LogoutButtonWithTooltip = () => { onCompleted: () => { clearKeys(); localStorage.removeItem(LOCALSTORAGE_KEYS.currentWalletId); + localStorage.removeItem('pw'); window.location.assign(ROUTES.home); }, onError: () => @@ -62,6 +63,7 @@ export const LogoutButton = () => { onCompleted: () => { clearKeys(); localStorage.removeItem(LOCALSTORAGE_KEYS.currentWalletId); + localStorage.removeItem('pw'); window.location.assign(ROUTES.home); }, onError: () => diff --git a/src/components/form/LoginForm.tsx b/src/components/form/LoginForm.tsx index 653c7027..a1d343db 100644 --- a/src/components/form/LoginForm.tsx +++ b/src/components/form/LoginForm.tsx @@ -3,6 +3,7 @@ import { zodResolver } from '@hookform/resolvers/zod'; import { startAuthentication } from '@simplewebauthn/browser'; import { PublicKeyCredentialRequestOptionsJSON } from '@simplewebauthn/types'; +import stringEntropy from 'fast-password-entropy'; import { Loader2 } from 'lucide-react'; import Image from 'next/image'; import Link from 'next/link'; @@ -55,6 +56,8 @@ export const LoginForm = () => { const [login, { data }] = useLoginMutation({ onCompleted: data => { + localStorage.setItem('pw', entropy.toString()); + if (data.login.initial.two_factor?.methods.find(m => m.enabled)) { setView('2fa'); } else { @@ -81,6 +84,8 @@ export const LoginForm = () => { }, }); + const entropy = stringEntropy(form.getValues().password); + const onSubmit = async (data: z.infer) => { if (loading) return; @@ -205,6 +210,7 @@ export const LoginForm = () => { ( {c('email')} @@ -219,6 +225,7 @@ export const LoginForm = () => { ( {c('password')} diff --git a/src/components/form/SignUpForm.tsx b/src/components/form/SignUpForm.tsx index 5342c21d..016593fb 100644 --- a/src/components/form/SignUpForm.tsx +++ b/src/components/form/SignUpForm.tsx @@ -135,6 +135,8 @@ export function SignUpForm() { return; } + localStorage.setItem('pw', entropy.toString()); + window.location.href = ROUTES.dashboard; break; @@ -160,7 +162,7 @@ export function SignUpForm() { return () => { if (workerRef.current) workerRef.current.terminate(); }; - }, [client, toast]); + }, [client, toast, entropy]); return view === 'waitlist' ? ( @@ -259,7 +261,7 @@ export function SignUpForm() { {s('set')} -

+

{s('save')}

diff --git a/src/components/toggle/LanguageToggle.tsx b/src/components/toggle/LanguageToggle.tsx index d24bb6ac..3bcc97f9 100644 --- a/src/components/toggle/LanguageToggle.tsx +++ b/src/components/toggle/LanguageToggle.tsx @@ -1,10 +1,10 @@ 'use client'; -import { Globe } from 'lucide-react'; +import { ChevronsUpDown, Globe } from 'lucide-react'; import { useRouter } from 'next/navigation'; +import { useLocale } from 'next-intl'; import * as React from 'react'; -import { useState } from 'react'; -import { useIsClient } from 'usehooks-ts'; +import { FC, useState } from 'react'; import { DropdownMenu, @@ -14,23 +14,26 @@ import { } from '@/components/ui/dropdown-menu'; import { SupportedLanguage } from '@/i18n'; import { cn } from '@/utils/cn'; +import { localeToLanguage } from '@/views/settings/Settings'; import { Button } from '../ui/button-v2'; -const getCookie = () => +export const getCookie = () => document.cookie .split('; ') .find(c => c.startsWith('locale=')) ?.split('=')[1] as SupportedLanguage | undefined; -const setCookie = (locale: SupportedLanguage) => +export const setCookie = (locale: SupportedLanguage) => (document.cookie = `locale=${locale}; max-age=31536000; path=/;`); -const deleteCookie = () => +export const deleteCookie = () => (document.cookie = 'locale=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;'); -export function LanguageToggle() { - const isClient = useIsClient(); +export const LanguageToggle: FC<{ type?: 'compact' | 'select' }> = ({ + type = 'compact', +}) => { + const locale = useLocale(); const { refresh } = useRouter(); @@ -38,22 +41,26 @@ export function LanguageToggle() { typeof window !== 'undefined' ? getCookie() : undefined ); - if (!isClient) return null; - return ( - + {type === 'compact' ? ( + + ) : type === 'select' ? ( + + ) : null} { deleteCookie(); setLanguage(undefined); - window.location.reload(); + refresh(); }} >

System

@@ -86,4 +93,4 @@ export function LanguageToggle() {
); -} +}; diff --git a/src/components/toggle/ThemeToggle.tsx b/src/components/toggle/ThemeToggle.tsx index aa457f14..b9f4f33e 100644 --- a/src/components/toggle/ThemeToggle.tsx +++ b/src/components/toggle/ThemeToggle.tsx @@ -1,38 +1,42 @@ 'use client'; -import { Moon, Sun } from 'lucide-react'; +import { ChevronsUpDown, Moon, Sun } from 'lucide-react'; import { useTheme } from 'next-themes'; import * as React from 'react'; -import { Button } from '@/components/ui/button'; import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; +import { cn } from '@/utils/cn'; export function ThemeToggle() { - const { setTheme } = useTheme(); + const { theme, setTheme } = useTheme(); return ( - + setTheme('light')}> - Light +

Light

+ +
setTheme('dark')}> - Dark +

Dark

+ +
setTheme('system')}> - System +

System

diff --git a/src/components/ui/form.tsx b/src/components/ui/form.tsx index cf811202..2610cecc 100644 --- a/src/components/ui/form.tsx +++ b/src/components/ui/form.tsx @@ -88,12 +88,12 @@ const FormLabel = React.forwardRef< React.ElementRef, React.ComponentPropsWithoutRef >(({ className, ...props }, ref) => { - const { error, formItemId } = useFormField(); + const { formItemId } = useFormField(); return (