feat(modal): Add some settings to enable two-factor authentication when turning on

- Introduced visual style variants ("ask", "warning", "danger") for the confirmation dialog in the modal component.
- Updated the `ask` method to accept a variant option, allowing for customized icon and button styles based on the selected variant.
- Enhanced the dialog's title rendering to include variant-specific icons for better user experience.
- Updated type definitions to include the new variant option for the dialog instance.

Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
Innei 2025-06-07 01:00:10 +08:00
parent 83697ce3fe
commit 2438f072fd
No known key found for this signature in database
GPG Key ID: 0F62D33977F021F7
9 changed files with 95 additions and 23 deletions

View File

@ -126,10 +126,45 @@ export const useDialog = (): DialogInstance => {
const { present } = useModalStack()
const { t } = useTranslation()
return {
/**
* Show a confirmation dialog with different visual variants
* @param options.variant - Visual style variant:
* - "ask" (default): Standard confirmation dialog
* - "warning": Warning dialog with yellow icon and yellow confirm button
* - "danger": Danger dialog with red icon and red confirm button
*/
ask: useEventCallback((options) => {
const variant = options.variant || "ask"
// Variant-specific configuration
const variantConfig = {
ask: {
icon: null,
confirmVariant: "primary" as const,
confirmClassName: "",
},
warning: {
icon: <i className="i-mingcute-warning-fill size-5 text-yellow-500" />,
confirmVariant: "primary" as const,
confirmClassName: "bg-yellow-600 hover:bg-yellow-700",
},
danger: {
icon: <i className="i-mingcute-warning-fill size-5 text-red-500" />,
confirmVariant: "primary" as const,
confirmClassName: "bg-red-600 hover:bg-red-700",
},
}
const config = variantConfig[variant]
return new Promise<boolean>((resolve) => {
present({
title: options.title,
title: (
<div className="flex items-center gap-2">
{config.icon}
<span>{options.title}</span>
</div>
),
content: ({ dismiss }) => (
<div className="flex max-w-[75ch] flex-col gap-3">
{options.message}
@ -146,6 +181,8 @@ export const useDialog = (): DialogInstance => {
{options.cancelText ?? t("words.cancel", { ns: "common" })}
</Button>
<Button
variant={config.confirmVariant}
buttonClassName={config.confirmClassName}
onClick={() => {
options.onConfirm?.()
resolve(true)

View File

@ -37,6 +37,7 @@ export interface DialogInstance {
ask: (options: {
title: string
message: string
variant?: "ask" | "warning" | "danger"
onConfirm?: () => void
onCancel?: () => void
confirmText?: string

View File

@ -34,10 +34,13 @@ export const SettingSwitch: Component<{
onCheckedChange: (checked: boolean) => void
}> = ({ checked, label, onCheckedChange, className }) => {
const id = useId()
const handleCheckedChange = (checked: boolean) => {
onCheckedChange(checked)
}
return (
<div className={cn("mb-3 flex items-center justify-between gap-4", className)}>
<Label htmlFor={id}>{titleCase(label)}</Label>
<Switch id={id} checked={checked} onCheckedChange={onCheckedChange} />
<Switch id={id} checked={checked} onCheckedChange={handleCheckedChange} />
</div>
)
}

View File

@ -15,6 +15,7 @@ export type SettingItem<T, K extends keyof T = keyof T> = {
label: string
description?: string
onChange: (value: T[K]) => void
onChangeGuard?: (value: T[K]) => "handled" | void
type?: "password"
vertical?: boolean
@ -92,7 +93,15 @@ export const createSettingBuilder =
<SettingSwitch
className="mt-4"
checked={settingObject[assertSetting.key] as boolean}
onCheckedChange={(checked) => assertSetting.onChange(checked as T[keyof T])}
onCheckedChange={(checked) => {
if (assertSetting.onChangeGuard) {
const handled = assertSetting.onChangeGuard(checked as T[keyof T])
if (handled === "handled") {
return
}
assertSetting.onChange(checked as T[keyof T])
}
}}
label={assertSetting.label}
/>
)

View File

@ -20,6 +20,7 @@ import {
useGeneralSettingSelector,
useGeneralSettingValue,
} from "~/atoms/settings/general"
import { useDialog } from "~/components/ui/modal/stacked/hooks"
import { useProxyValue, useSetProxy } from "~/hooks/biz/useProxySetting"
import { useMinimizeToTrayValue, useSetMinimizeToTray } from "~/hooks/biz/useTraySetting"
import { fallbackLanguage } from "~/i18n"
@ -65,6 +66,7 @@ export const SettingGeneral = () => {
const isMobile = useMobile()
const { ask } = useDialog()
const reRenderKey = useGeneralSettingKey("enhancedSettings")
return (
@ -167,9 +169,25 @@ export const SettingGeneral = () => {
IN_ELECTRON && NettingSetting,
{ type: "title", value: t("general.advanced") },
defineSettingItem("enhancedSettings", {
label: t("general.enhanced.label"),
description: t("general.enhanced.description"),
onChangeGuard(value) {
if (value) {
ask({
variant: "danger",
title: t("general.enhanced.enable.modal.title"),
message: t("general.enhanced.enable.modal.description"),
confirmText: t("general.enhanced.enable.modal.confirm"),
cancelText: t("general.enhanced.enable.modal.cancel"),
onConfirm: () => {
setGeneralSetting("enhancedSettings", value)
},
})
return "handled"
}
},
}),
]}
/>

View File

@ -167,16 +167,20 @@
"general.app": "App",
"general.auto_expand_long_social_media.description": "Automatically expand social media entries containing long text.",
"general.auto_expand_long_social_media.label": "Expand long social media",
"general.auto_group.description": "Automatically group feeds by site domain.",
"general.auto_group.label": "Auto Group",
"general.auto_group.description": "Group feeds from the same website domain together.",
"general.auto_group.label": "Auto Group feeds by site",
"general.cache": "Cache",
"general.content": "Content",
"general.data": "Data",
"general.data_file.label": "Data File",
"general.data_persist.description": "Persist data locally to enable offline access and local search.",
"general.data_persist.label": "Persist data for offline usage",
"general.enhanced.description": "Enabling the enhanced settings offers more customization options, but it may also introduce unforeseen issues.",
"general.enhanced.description": "Enabling the enhanced settings offers more customization options, but may also introduce unforeseen issues. !!! Keep this as the gateway to truly experimental/complex things if needed later, otherwise it might be removable if other settings cover specifics.",
"general.enhanced.disabled.tip": "Enhanced settings are disabled, you can enable them in the General settings - Advanced.",
"general.enhanced.enable.modal.cancel": "Cancel",
"general.enhanced.enable.modal.confirm": "Enable",
"general.enhanced.enable.modal.description": "Enabling the enhanced settings offers more customization options, but may also introduce unforeseen issues. Do not enable this unless you know what you are doing.",
"general.enhanced.enable.modal.title": "Enable Enhanced Settings",
"general.enhanced.enabled.tip": "Enhanced settings are enabled, you can disable them in the General settings - Advanced.",
"general.enhanced.label": "Enhanced Settings",
"general.export.button": "Export",
@ -204,11 +208,11 @@
"general.log_file.description": "Reveal the log file in the system.",
"general.log_file.label": "Log File",
"general.mark_as_read.hover.description": "Automatically mark entries as read when hovered.",
"general.mark_as_read.hover.label": "Mark as read when hovering",
"general.mark_as_read.render.description": "Automatically mark single-level entries (e.g. social media posts, pictures, video views) as read when they enter the view.",
"general.mark_as_read.render.label": "Mark as read when in the view",
"general.mark_as_read.hover.label": "When hovering over article",
"general.mark_as_read.render.description": "Mark items like social posts or images as read immediately.",
"general.mark_as_read.render.label": "Single items when they enter view",
"general.mark_as_read.scroll.description": "Automatically mark entries as read when scrolled out of the view.",
"general.mark_as_read.scroll.label": "Mark as read when scrolling",
"general.mark_as_read.scroll.label": "When scrolling past article",
"general.mark_as_read.title": "Mark as read",
"general.minimize_to_tray.description": "Minimize to system tray when closing window",
"general.minimize_to_tray.label": "Minimize to tray",
@ -228,7 +232,7 @@
"general.send_anonymous_data.label": "Send anonymous data",
"general.show_quick_timeline.description": "Show the quick timeline at the top of the feed list.",
"general.show_quick_timeline.label": "Show feed list timeline",
"general.show_unread_on_launch.description": "Only show unread entries when you open the app.",
"general.show_unread_on_launch.description": "Automatically filter to unread content when the app starts",
"general.show_unread_on_launch.label": "Unread only on launch",
"general.sidebar_title": "General",
"general.startup_screen.subscription": "Subscription",

View File

@ -201,11 +201,11 @@
"general.log_file.description": "システムにログファイルがあると表示します。",
"general.log_file.label": "ログファイル",
"general.mark_as_read.hover.description": "ホバー時にエントリを自動的に既読にします。",
"general.mark_as_read.hover.label": "ホバーで既読にする",
"general.mark_as_read.render.description": "表示されると、単一レベルのエントリ(例:ソーシャルメディアの投稿、画像、動画)が自動的に既読になります。",
"general.mark_as_read.render.label": "表示中に既読にする",
"general.mark_as_read.hover.label": "記事にホバーしたとき",
"general.mark_as_read.render.description": "ソーシャルメディアの投稿や画像などの単一項目を即座に既読にマークします。",
"general.mark_as_read.render.label": "単一項目がビューに入ったとき",
"general.mark_as_read.scroll.description": "表示からスクロールアウトしたときにエントリを自動的に既読にします。",
"general.mark_as_read.scroll.label": "スクロール時に既読にする",
"general.mark_as_read.scroll.label": "記事をスクロールして通り過ぎたとき",
"general.mark_as_read.title": "既読にする",
"general.minimize_to_tray.description": "ウィンドウを閉じるとシステムトレイに最小化します",
"general.minimize_to_tray.label": "トレイに最小化",

View File

@ -201,11 +201,11 @@
"general.log_file.description": "在系统中显示日志文件。",
"general.log_file.label": "日志文件",
"general.mark_as_read.hover.description": "当鼠标悬停在条目时自动将其标记为已读。",
"general.mark_as_read.hover.label": "悬停标记为已读",
"general.mark_as_read.render.description": "当单级条目(例如社交媒体贴文、图片、视频观看)进入可视区域时自动将其标记为已读。",
"general.mark_as_read.render.label": "在可视区域中时标记为已读",
"general.mark_as_read.hover.label": "悬停在文章上时",
"general.mark_as_read.render.description": "将社交媒体贴文或图片等单项内容立即标记为已读。",
"general.mark_as_read.render.label": "单项内容进入视图时",
"general.mark_as_read.scroll.description": "当条目滚动出可视区域时自动将其标记为已读。",
"general.mark_as_read.scroll.label": "滚动标记为已读",
"general.mark_as_read.scroll.label": "滚动浏览文章时",
"general.mark_as_read.title": "标记已读",
"general.minimize_to_tray.description": "关闭窗口时最小化到系统托盘。",
"general.minimize_to_tray.label": "最小化到托盘",

View File

@ -201,11 +201,11 @@
"general.log_file.description": "在系統中顯示記錄檔案。",
"general.log_file.label": "記錄檔案",
"general.mark_as_read.hover.description": "游標懸停時自動將條目標記為已讀。",
"general.mark_as_read.hover.label": "滑過時標記為已讀",
"general.mark_as_read.render.description": "當單層級條目(例如社群媒體貼文、圖片、影片瀏覽)進入視圖時自動標記為已讀。",
"general.mark_as_read.render.label": "顯示時標記為已讀",
"general.mark_as_read.hover.label": "滑鼠懸停在文章上時",
"general.mark_as_read.render.description": "將社群媒體貼文或圖片等單項內容立即標記為已讀。",
"general.mark_as_read.render.label": "單項內容進入視圖時",
"general.mark_as_read.scroll.description": "當條目捲動離開視圖時自動標記為已讀。",
"general.mark_as_read.scroll.label": "捲動標記為已讀",
"general.mark_as_read.scroll.label": "捲動瀏覽文章時",
"general.mark_as_read.title": "標記為已讀",
"general.minimize_to_tray.description": "關閉視窗時最小化到工作列通知區域",
"general.minimize_to_tray.label": "最小化到通知區域",