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

feat: auto-lock in quick settings #1684

Merged
merged 2 commits into from
Nov 14, 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
57 changes: 57 additions & 0 deletions apps/extension/src/@talisman/components/ExclusiveButtonsList.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { CheckIcon } from "@talismn/icons"
import { classNames } from "@talismn/util"
import { FC } from "react"

type ExclusiveButtonsListProps<T> = {
options: ExclusiveButtonsListItemProps<T>[]
value: T
onChange: (value: T) => void
className?: string
}

type ExclusiveButtonsListItemProps<T> = {
value: T
label: string
}

export const ExclusiveButtonsList = <T extends string | number>({
options,
value,
onChange,
className,
}: ExclusiveButtonsListProps<T>) => {
return (
<div className={classNames("flex flex-col gap-4", className)}>
{options.map((option) => (
<Button
key={option.value}
displayName={option.label}
selected={option.value === value}
onClick={() => onChange(option.value)}
/>
))}
</div>
)
}

const Button: FC<{
displayName: string
selected: boolean
onClick: () => void
}> = ({ displayName, selected, onClick }) => {
return (
<button
type="button"
className={classNames(
"text-body-secondary flex h-28 w-full items-center justify-between gap-4 rounded-sm px-6 sm:px-8",
"border-grey-800 border",
selected && "bg-grey-900 text-body",
"hover:border-grey-700 hover:bg-grey-800 stroke-primary",
)}
onClick={onClick}
>
<div>{displayName}</div>
{!!selected && <CheckIcon className="text-primary text-base sm:text-lg" />}
</button>
)
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { useCallback, useMemo } from "react"
import { useMemo } from "react"
import { useTranslation } from "react-i18next"
import { Dropdown } from "talisman-ui"

import { ExclusiveButtonsList } from "@talisman/components/ExclusiveButtonsList"
import { HeaderBlock } from "@talisman/components/HeaderBlock"
import { Spacer } from "@talisman/components/Spacer"
import { useSetting } from "@ui/state"
Expand All @@ -11,12 +11,13 @@ import { DashboardLayout } from "../../layout"
type Option = { value: number; label: string }

export const Content = () => {
const { t } = useTranslation("admin")
const { t } = useTranslation()
const [autoLockTimeout, setAutoLockTimeout] = useSetting("autoLockMinutes")

const options: Option[] = useMemo(
() => [
{ value: 0, label: t("Disabled") },
{ value: 1, label: t("{{count}} minute", { count: 1 }) },
{ value: 5, label: t("{{count}} minutes", { count: 5 }) },
{ value: 15, label: t("{{count}} minutes", { count: 15 }) },
{ value: 30, label: t("{{count}} minutes", { count: 30 }) },
Expand All @@ -25,33 +26,19 @@ export const Content = () => {
[t],
)

const handleChange = useCallback(
(val: Option | null) => {
const newVal = val?.value || 0
if (newVal !== autoLockTimeout) setAutoLockTimeout(newVal)
},
[autoLockTimeout, setAutoLockTimeout],
)

const value = useMemo(
() => options.find((o) => o.value === (autoLockTimeout ?? 0)),
[autoLockTimeout, options],
)

return (
<>
<HeaderBlock
title="Auto-lock Timer"
text="Set a timer to automatically lock the Talisman wallet extension."
title={t("Auto-lock Timer")}
text={t(
"Set a timer to automatically lock the Talisman wallet extension after the following period of inactivity",
)}
/>
<Spacer />
<Dropdown
label={t("Lock the Talisman extension after inactivity for")}
items={options}
value={value}
propertyKey="value"
propertyLabel="label"
onChange={handleChange}
<ExclusiveButtonsList
options={options}
value={autoLockTimeout}
onChange={setAutoLockTimeout}
/>
<Spacer />
</>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,63 +1,31 @@
import { CheckIcon } from "@talismn/icons"
import { useMemo } from "react"
import { useTranslation } from "react-i18next"

import { languages } from "@common/i18nConfig"
import { ExclusiveButtonsList } from "@talisman/components/ExclusiveButtonsList"
import { HeaderBlock } from "@talisman/components/HeaderBlock"
import { Spacer } from "@talisman/components/Spacer"

import { DashboardLayout } from "../../layout"

const LanguageButton = ({
displayName,
lang,
isCurrent,
onClick,
}: {
displayName: string
lang?: keyof typeof languages
isCurrent?: boolean
onClick?: (lang?: keyof typeof languages) => void
}) => (
<button
type="button"
key={lang}
className="bg-grey-900 enabled:hover:bg-grey-800 text-body-disabled enabled:hover:text-body flex h-28 w-full cursor-pointer items-center gap-8 rounded-sm px-8 disabled:cursor-not-allowed disabled:opacity-50"
onClick={() => onClick && onClick(lang)}
>
<div className="flex grow flex-col items-start gap-4">
<div className="text-body">{displayName}</div>
</div>
{isCurrent && <CheckIcon className="text-lg" />}
</button>
)

const Content = () => {
const { t, i18n } = useTranslation("admin")
const { t, i18n } = useTranslation()

const currentLang = i18n.language
const changeLang = (lang?: keyof typeof languages) => lang && i18n.changeLanguage(lang)
const options = useMemo(
() => Object.entries(languages).map(([value, label]) => ({ value, label })),
[],
)

return (
<>
<HeaderBlock title={t("Language")} text={t("Choose your preferred language")} />
<Spacer />
<div className="flex flex-col gap-4">
{/* Fallback for unknown languages ( Shouldn't happen, but hey ¯\_(ツ)_/¯ ) */}
{!Object.keys(languages).includes(currentLang) && (
<LanguageButton displayName={t("Unknown")} isCurrent />
)}

{/* List of languages */}
{Object.entries(languages).map(([lang, displayName]) => (
<LanguageButton
key={lang}
lang={lang as keyof typeof languages}
displayName={displayName}
isCurrent={lang === currentLang}
onClick={changeLang}
/>
))}
</div>
<ExclusiveButtonsList
options={options}
value={i18n.language}
onChange={i18n.changeLanguage}
/>
<Spacer />
</>
)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { ChevronLeftIcon } from "@talismn/icons"
import { useCallback, useMemo } from "react"
import { useTranslation } from "react-i18next"
import { Drawer, IconButton } from "talisman-ui"

import { ExclusiveButtonsList } from "@talisman/components/ExclusiveButtonsList"
import { ScrollContainer } from "@talisman/components/ScrollContainer"
import { useGlobalOpenClose } from "@talisman/hooks/useGlobalOpenClose"
import { useSetting } from "@ui/state"

export const useAutoLockDrawerOpenClose = () => useGlobalOpenClose("auto-lock-drawer")

const AutoLockEditor = () => {
const { t } = useTranslation()
const { close } = useAutoLockDrawerOpenClose()
const [autoLockTimeout, setAutoLockTimeout] = useSetting("autoLockMinutes")

const options = useMemo(
() => [
{ value: 0, label: t("Disabled") },
{ value: 5, label: t("{{count}} minutes", { count: 5 }) },
{ value: 15, label: t("{{count}} minutes", { count: 15 }) },
{ value: 30, label: t("{{count}} minutes", { count: 30 }) },
{ value: 60, label: t("{{count}} minutes", { count: 60 }) },
],
[t],
)

const handleChange = useCallback(
(value: number) => {
setAutoLockTimeout(value)
close()
},
[close, setAutoLockTimeout],
)

return <ExclusiveButtonsList options={options} value={autoLockTimeout} onChange={handleChange} />
}

const AutoLockDrawerContent = () => {
const { t } = useTranslation()
const { close } = useAutoLockDrawerOpenClose()

return (
<div className="text-body-secondary flex h-[60rem] w-[40rem] flex-col gap-10 bg-black pt-10">
<div className="flex items-center gap-3 px-8 text-base font-bold text-white">
<IconButton onClick={close}>
<ChevronLeftIcon />
</IconButton>
<div>{t("Auto-lock Timer")}</div>
</div>

<div className="px-8">
<p className="text-xs">
{t(
"Set a timer to automatically lock the Talisman wallet extension after the following period of inactivity",
)}
</p>
</div>
<ScrollContainer className="grow" innerClassName="px-8 pb-8">
<AutoLockEditor />
</ScrollContainer>
</div>
)
}

export const AutoLockDrawer = () => {
const { isOpen } = useAutoLockDrawerOpenClose()

return (
<Drawer anchor="right" isOpen={isOpen} containerId="main">
<AutoLockDrawerContent />
</Drawer>
)
}
Original file line number Diff line number Diff line change
@@ -1,64 +1,34 @@
import { CheckIcon, ChevronLeftIcon } from "@talismn/icons"
import { classNames } from "@talismn/util"
import { FC, useCallback, useMemo } from "react"
import { ChevronLeftIcon } from "@talismn/icons"
import { useCallback, useMemo } from "react"
import { useTranslation } from "react-i18next"
import { Drawer, IconButton } from "talisman-ui"

import { languages } from "@common/i18nConfig"
import { ExclusiveButtonsList } from "@talisman/components/ExclusiveButtonsList"
import { ScrollContainer } from "@talisman/components/ScrollContainer"
import { useGlobalOpenClose } from "@talisman/hooks/useGlobalOpenClose"

export const useLanguageDrawerOpenClose = () => useGlobalOpenClose("language-drawer")

type Language = keyof typeof languages

const LanguageButton: FC<{
displayName: string
selected: boolean
onClick: () => void
}> = ({ displayName, selected, onClick }) => {
return (
<button
type="button"
className={classNames(
"text-body-secondary flex h-28 w-full items-center justify-between gap-4 rounded-sm px-6",
"border-grey-800 border",
selected && "bg-grey-900 text-body",
"hover:border-grey-700 hover:bg-grey-800 stroke-primary",
)}
onClick={onClick}
>
<div>{displayName}</div>
{!!selected && <CheckIcon className="text-primary size-8" />}
</button>
)
}

const LanguagesList = () => {
const { i18n } = useTranslation()
const { close } = useLanguageDrawerOpenClose()

const currentLang = useMemo(() => i18n.language, [i18n.language])
const options = useMemo(
() => Object.entries(languages).map(([value, label]) => ({ value, label })),
[],
)

const handleLanguageClick = useCallback(
(lang: Language) => () => {
(lang: string) => {
i18n.changeLanguage(lang ?? "en")
close()
},
[close, i18n],
)

return (
<div className="flex flex-col gap-4">
{Object.entries(languages).map(([lang, displayName]) => (
<LanguageButton
key={lang}
displayName={displayName}
selected={lang === currentLang}
onClick={handleLanguageClick(lang)}
/>
))}
</div>
<ExclusiveButtonsList options={options} value={i18n.language} onChange={handleLanguageClick} />
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { currencyConfig } from "@ui/domains/Asset/currencyConfig"
import { useFavoriteCurrencies } from "@ui/hooks/useFavoriteCurrencies"
import { useSetting } from "@ui/state"

import { AutoLockDrawer, useAutoLockDrawerOpenClose } from "./AutoLockDrawer"
import { CurrenciesDrawer, useCurrenciesDrawerOpenClose } from "./CurrenciesDrawer"
import { LanguageDrawer, useLanguageDrawerOpenClose } from "./LanguageDrawer"

Expand Down Expand Up @@ -69,6 +70,7 @@ export const QuickSettingsModal: FC = () => {
<div className="flex w-full flex-col">
<LanguageRow />
<CurrenciesRow />
<AutoLockRow />
<HideBalancesRow />
<HideSmallBalancesRow />
<ShowTestnetsRow />
Expand Down Expand Up @@ -107,6 +109,30 @@ const LanguageRow = () => {
)
}

const AutoLockRow = () => {
const { t } = useTranslation()
const { open } = useAutoLockDrawerOpenClose()

const [autoLockMinutes] = useSetting("autoLockMinutes")
const display = useMemo(() => {
if (autoLockMinutes === 0) return t("Disabled")
return t("{{minutes}} minutes", { minutes: autoLockMinutes })
}, [autoLockMinutes, t])

return (
<SettingRow label={t("Auto-lock timer")}>
<button
type="button"
className="text-grey-300 hover:text-body text-sm font-bold"
onClick={open}
>
{display}
</button>
<AutoLockDrawer />
</SettingRow>
)
}

const CurrenciesRow = () => {
const { t } = useTranslation()
const { open } = useCurrenciesDrawerOpenClose()
Expand Down
Loading