Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[AMB-157] refactor: settings redesign #96

Merged
merged 7 commits into from
Oct 31, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,60 @@
"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",
"warning": "In order to send USD you need to add Bitcoin to your wallet.",
"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.",
Expand Down
43 changes: 43 additions & 0 deletions messages/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,60 @@
"Dashboard": {
"asset": "Un activo en tu billetera.",
"buy": "Comprar",
"go": "Ir",
"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": "Proteje tu cuenta con una contraseña más fuerte.",
"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",
"warning": "Para enviar USD tienes que tener Bitcoin en tu billetera.",
"warning-title": "Agrega Bitcoin!",
"what-asset": "Qué es {asset}?"
},
"Settings": {
"2fa": "Autenticación de dos factores",
"add-passkey": "Agregar clave de acceso",
"added": "Añadido",
"appearance": "Apariencia",
"auth-app": "Aplicación de autenticación",
"change-pass": "Cambiar contraseña",
"current-pass": "Contraseña actual",
"dark": "Oscuro",
"dark-mode": "Modo oscuro",
"display": "Modo de visualización",
"enable-encrypt": "Habilitar encripción",
"enabled": "Habilitado",
"encrypt-enabled": "Encripción habilitada",
"enter-otp": "Ingrese la contraseña de un solo uso:",
"good": "Bien",
"lang": "Idioma preferido",
"language": "Idioma",
"light": "Claro",
"light-mode": "Modo claro",
"login-only": "Solo iniciar sesión",
"login-wallet": "Inicio de sesión y billetera",
"new-pass": "Nueva contraseña",
"no-scan": "¿No tiene escáner de código QR? Ingrese el texto a continuación:",
"not-set": "No establecido",
"off": "APAGADO",
"otp": "Escanee el código QR a continuación con su aplicación de autenticación preferida o ingrese manualmente el código proporcionado. Una vez configurado, deberá ingresar una contraseña de un solo uso (OTP) generada por la aplicación cada vez que inicie sesión.",
"pass-confirm": "He guardado mi contraseña y entiendo que si la pierdo no podré recuperar mi cuenta y el acceso a mis fondos se perderá para siempre.",
"passkeys": "Claves de acceso",
"saved-passkeys": "Claves de acceso guardadas",
"setup": "Configurar",
"setup-2fa": "Configurar 2FA",
"strong": "Fuerte",
"system": "Sistema",
"system-mode": "Modo sistema",
"unknown-mode": "Modo desconocido",
"unlock-encrypt": "Desbloquear para cifrar",
"very-strong": "Muy fuerte",
"very-weak": "Muy débil",
"weak": "Débil"
},
"Wallet": {
"Settings": {
"decrypt": "Desencriptar para ver la frase secreta de tu billetera.",
Expand Down
5 changes: 5 additions & 0 deletions src/app/(app)/(layout)/settings/appearance/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Appearance } from '@/views/settings/Appearance';

export default function Page() {
return <Appearance />;
}
5 changes: 5 additions & 0 deletions src/app/(app)/(layout)/settings/language/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Language } from '@/views/settings/Language';

export default function Page() {
return <Language />;
}
5 changes: 5 additions & 0 deletions src/app/(app)/(layout)/settings/passkeys/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Passkeys } from '@/views/settings/Passkeys';

export default function Page() {
return <Passkeys />;
}
5 changes: 5 additions & 0 deletions src/app/(app)/(layout)/settings/password/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { Password } from '@/views/settings/Password';

export default function Page() {
return <Password />;
}
2 changes: 2 additions & 0 deletions src/components/button/LogoutButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export const LogoutButtonWithTooltip = () => {
onCompleted: () => {
clearKeys();
localStorage.removeItem(LOCALSTORAGE_KEYS.currentWalletId);
localStorage.removeItem('pw');
window.location.assign(ROUTES.home);
},
onError: () =>
Expand Down Expand Up @@ -62,6 +63,7 @@ export const LogoutButton = () => {
onCompleted: () => {
clearKeys();
localStorage.removeItem(LOCALSTORAGE_KEYS.currentWalletId);
localStorage.removeItem('pw');
window.location.assign(ROUTES.home);
},
onError: () =>
Expand Down
7 changes: 7 additions & 0 deletions src/components/form/LoginForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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 {
Expand All @@ -81,6 +84,8 @@ export const LoginForm = () => {
},
});

const entropy = stringEntropy(form.getValues().password);

const onSubmit = async (data: z.infer<typeof FormSchema>) => {
if (loading) return;

Expand Down Expand Up @@ -205,6 +210,7 @@ export const LoginForm = () => {
<FormField
control={form.control}
name="email"
disabled={disabled}
render={({ field }) => (
<FormItem>
<FormLabel>{c('email')}</FormLabel>
Expand All @@ -219,6 +225,7 @@ export const LoginForm = () => {
<FormField
control={form.control}
name="password"
disabled={disabled}
render={({ field }) => (
<FormItem>
<FormLabel>{c('password')}</FormLabel>
Expand Down
6 changes: 4 additions & 2 deletions src/components/form/SignUpForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ export function SignUpForm() {
return;
}

localStorage.setItem('pw', entropy.toString());

window.location.href = ROUTES.dashboard;

break;
Expand All @@ -160,7 +162,7 @@ export function SignUpForm() {
return () => {
if (workerRef.current) workerRef.current.terminate();
};
}, [client, toast]);
}, [client, toast, entropy]);

return view === 'waitlist' ? (
<WaitlistForm setView={setView} setSubscriber={setSubscriber} />
Expand Down Expand Up @@ -259,7 +261,7 @@ export function SignUpForm() {
{s('set')}
</h1>

<p className="mb-6 rounded-xl border border-orange-400 px-4 py-2 text-sm">
<p className="mb-6 rounded-xl border border-orange-500 px-4 py-2 text-sm dark:border-orange-400">
{s('save')}
</p>

Expand Down
53 changes: 30 additions & 23 deletions src/components/toggle/LanguageToggle.tsx
Original file line number Diff line number Diff line change
@@ -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,
Expand All @@ -14,46 +14,53 @@ 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();

const [language, setLanguage] = useState<SupportedLanguage | undefined>(
typeof window !== 'undefined' ? getCookie() : undefined
);

if (!isClient) return null;

return (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="neutral"
size="sm"
className="flex items-center space-x-1"
>
<Globe size={16} />
<p className="uppercase">
{language || document.documentElement.lang}
</p>
<span className="sr-only">Toggle theme</span>
</Button>
{type === 'compact' ? (
<Button
variant="neutral"
size="sm"
className="flex items-center space-x-1"
>
<Globe size={16} />
<p className="uppercase">{language || locale}</p>
<span className="sr-only">Toggle theme</span>
</Button>
) : type === 'select' ? (
<button className="flex h-10 w-full items-center justify-between space-x-2 rounded-xl border border-slate-200 px-4 dark:border-neutral-800">
<p>{localeToLanguage(language)}</p>

<ChevronsUpDown size={16} />
</button>
) : null}
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem
Expand All @@ -78,12 +85,12 @@ export function LanguageToggle() {
onClick={() => {
deleteCookie();
setLanguage(undefined);
window.location.reload();
refresh();
}}
>
<p className={cn(!language && 'font-bold')}>System</p>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
);
}
};
26 changes: 15 additions & 11 deletions src/components/toggle/ThemeToggle.tsx
Original file line number Diff line number Diff line change
@@ -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 (
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="outline" size="icon">
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">Toggle theme</span>
</Button>
<button className="flex h-10 w-full items-center justify-between space-x-2 rounded-xl border border-slate-200 px-4 dark:border-neutral-800">
<p className="capitalize">{theme}</p>

<ChevronsUpDown size={16} />
</button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setTheme('light')}>
Light
<p className={cn(theme === 'light' && 'font-bold')}>Light</p>

<Sun size={16} className="ml-2" />
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme('dark')}>
Dark
<p className={cn(theme === 'dark' && 'font-bold')}>Dark</p>

<Moon size={16} className="ml-2" />
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme('system')}>
System
<p className={cn(theme === 'system' && 'font-bold')}>System</p>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
Expand Down
Loading
Loading