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:
parent
83697ce3fe
commit
2438f072fd
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ export interface DialogInstance {
|
|||
ask: (options: {
|
||||
title: string
|
||||
message: string
|
||||
variant?: "ask" | "warning" | "danger"
|
||||
onConfirm?: () => void
|
||||
onCancel?: () => void
|
||||
confirmText?: string
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
/>
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
},
|
||||
}),
|
||||
]}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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": "トレイに最小化",
|
||||
|
|
|
|||
|
|
@ -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": "最小化到托盘",
|
||||
|
|
|
|||
|
|
@ -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": "最小化到通知區域",
|
||||
|
|
|
|||
Loading…
Reference in New Issue