From 77ece5224da1f3531ec45a7419f41036e09df8e7 Mon Sep 17 00:00:00 2001 From: "Mr.Dat" Date: Wed, 4 Mar 2026 18:32:17 +0700 Subject: [PATCH] refactor: replace PrimeVue components with custom App components for buttons, dialogs, and inputs - Updated DangerZone.vue to use AppButton and AppDialog, replacing PrimeVue Button and Dialog components. - Refactored DomainsDns.vue to utilize AppButton, AppDialog, and AppInput, enhancing the UI consistency. - Modified NotificationSettings.vue and PlayerSettings.vue to implement AppButton and AppSwitch for better styling. - Replaced PrimeVue components in SecurityNConnected.vue with AppButton, AppDialog, and AppInput for a cohesive design. - Introduced AppConfirmHost for handling confirmation dialogs with a custom design. - Created AppToastHost for managing toast notifications with custom styling and behavior. - Added utility composables useAppConfirm and useAppToast for managing confirmation dialogs and toast notifications. - Implemented AppProgressBar and AppSwitch components for improved UI elements. --- components.d.ts | 28 +-- src/components/app/AppButton.vue | 66 ++++++ src/components/app/AppConfirmHost.vue | 47 +++++ src/components/app/AppDialog.vue | 97 +++++++++ src/components/app/AppInput.vue | 91 +++++++++ src/components/app/AppProgressBar.vue | 21 ++ src/components/app/AppSwitch.vue | 46 +++++ src/components/app/AppToastHost.vue | 101 +++++++++ src/composables/useAppConfirm.ts | 86 ++++++++ src/composables/useAppToast.ts | 64 ++++++ src/routes/settings/Settings.vue | 9 + .../components/ConnectedAccountsCard.vue | 143 +++++++------ .../components/ProfileInformationCard.vue | 116 +++++------ .../components/SecuritySettingsCard.vue | 83 +++----- src/routes/settings/pages/AdsVast.vue | 125 ++++++------ src/routes/settings/pages/Billing.vue | 192 ++++++++---------- src/routes/settings/pages/DangerZone.vue | 50 ++--- src/routes/settings/pages/DomainsDns.vue | 112 +++++----- .../settings/pages/NotificationSettings.vue | 25 ++- src/routes/settings/pages/PlayerSettings.vue | 25 ++- .../settings/pages/SecurityNConnected.vue | 186 ++++++++--------- 21 files changed, 1137 insertions(+), 576 deletions(-) create mode 100644 src/components/app/AppButton.vue create mode 100644 src/components/app/AppConfirmHost.vue create mode 100644 src/components/app/AppDialog.vue create mode 100644 src/components/app/AppInput.vue create mode 100644 src/components/app/AppProgressBar.vue create mode 100644 src/components/app/AppSwitch.vue create mode 100644 src/components/app/AppToastHost.vue create mode 100644 src/composables/useAppConfirm.ts create mode 100644 src/composables/useAppToast.ts diff --git a/components.d.ts b/components.d.ts index 574e69d..8b553fe 100644 --- a/components.d.ts +++ b/components.d.ts @@ -17,6 +17,13 @@ declare module 'vue' { AdvertisementIcon: typeof import('./src/components/icons/AdvertisementIcon.vue')['default'] AlertTriangle: typeof import('./src/components/icons/AlertTriangle.vue')['default'] AlertTriangleIcon: typeof import('./src/components/icons/AlertTriangleIcon.vue')['default'] + AppButton: typeof import('./src/components/app/AppButton.vue')['default'] + AppConfirmHost: typeof import('./src/components/app/AppConfirmHost.vue')['default'] + AppDialog: typeof import('./src/components/app/AppDialog.vue')['default'] + AppInput: typeof import('./src/components/app/AppInput.vue')['default'] + AppProgressBar: typeof import('./src/components/app/AppProgressBar.vue')['default'] + AppSwitch: typeof import('./src/components/app/AppSwitch.vue')['default'] + AppToastHost: typeof import('./src/components/app/AppToastHost.vue')['default'] ArrowDownTray: typeof import('./src/components/icons/ArrowDownTray.vue')['default'] ArrowRightIcon: typeof import('./src/components/icons/ArrowRightIcon.vue')['default'] Bell: typeof import('./src/components/icons/Bell.vue')['default'] @@ -44,18 +51,13 @@ declare module 'vue' { HardDriveUpload: typeof import('./src/components/icons/HardDriveUpload.vue')['default'] HeartIcon: typeof import('./src/components/icons/HeartIcon.vue')['default'] Home: typeof import('./src/components/icons/Home.vue')['default'] - IconField: typeof import('primevue/iconfield')['default'] ImageIcon: typeof import('./src/components/icons/ImageIcon.vue')['default'] InfoIcon: typeof import('./src/components/icons/InfoIcon.vue')['default'] - InputIcon: typeof import('primevue/inputicon')['default'] - InputNumber: typeof import('primevue/inputnumber')['default'] InputText: typeof import('primevue/inputtext')['default'] - KeyIcon: typeof import('./src/components/icons/KeyIcon.vue')['default'] Layout: typeof import('./src/components/icons/Layout.vue')['default'] LayoutDashboard: typeof import('./src/components/icons/LayoutDashboard.vue')['default'] LinkIcon: typeof import('./src/components/icons/LinkIcon.vue')['default'] LockIcon: typeof import('./src/components/icons/LockIcon.vue')['default'] - LogOutIcon: typeof import('./src/components/icons/LogOutIcon.vue')['default'] MailIcon: typeof import('./src/components/icons/MailIcon.vue')['default'] Message: typeof import('primevue/message')['default'] MonitorIcon: typeof import('./src/components/icons/MonitorIcon.vue')['default'] @@ -74,14 +76,12 @@ declare module 'vue' { RouterView: typeof import('vue-router')['RouterView'] SendIcon: typeof import('./src/components/icons/SendIcon.vue')['default'] SettingsIcon: typeof import('./src/components/icons/SettingsIcon.vue')['default'] - ShieldCheckIcon: typeof import('./src/components/icons/ShieldCheckIcon.vue')['default'] Skeleton: typeof import('primevue/skeleton')['default'] SlidersIcon: typeof import('./src/components/icons/SlidersIcon.vue')['default'] StatsCard: typeof import('./src/components/dashboard/StatsCard.vue')['default'] Tag: typeof import('primevue/tag')['default'] TelegramIcon: typeof import('./src/components/icons/TelegramIcon.vue')['default'] TestIcon: typeof import('./src/components/icons/TestIcon.vue')['default'] - ToggleSwitch: typeof import('primevue/toggleswitch')['default'] TrashIcon: typeof import('./src/components/icons/TrashIcon.vue')['default'] Upload: typeof import('./src/components/icons/Upload.vue')['default'] UploadIcon: typeof import('./src/components/icons/UploadIcon.vue')['default'] @@ -105,6 +105,13 @@ declare global { const AdvertisementIcon: typeof import('./src/components/icons/AdvertisementIcon.vue')['default'] const AlertTriangle: typeof import('./src/components/icons/AlertTriangle.vue')['default'] const AlertTriangleIcon: typeof import('./src/components/icons/AlertTriangleIcon.vue')['default'] + const AppButton: typeof import('./src/components/app/AppButton.vue')['default'] + const AppConfirmHost: typeof import('./src/components/app/AppConfirmHost.vue')['default'] + const AppDialog: typeof import('./src/components/app/AppDialog.vue')['default'] + const AppInput: typeof import('./src/components/app/AppInput.vue')['default'] + const AppProgressBar: typeof import('./src/components/app/AppProgressBar.vue')['default'] + const AppSwitch: typeof import('./src/components/app/AppSwitch.vue')['default'] + const AppToastHost: typeof import('./src/components/app/AppToastHost.vue')['default'] const ArrowDownTray: typeof import('./src/components/icons/ArrowDownTray.vue')['default'] const ArrowRightIcon: typeof import('./src/components/icons/ArrowRightIcon.vue')['default'] const Bell: typeof import('./src/components/icons/Bell.vue')['default'] @@ -132,18 +139,13 @@ declare global { const HardDriveUpload: typeof import('./src/components/icons/HardDriveUpload.vue')['default'] const HeartIcon: typeof import('./src/components/icons/HeartIcon.vue')['default'] const Home: typeof import('./src/components/icons/Home.vue')['default'] - const IconField: typeof import('primevue/iconfield')['default'] const ImageIcon: typeof import('./src/components/icons/ImageIcon.vue')['default'] const InfoIcon: typeof import('./src/components/icons/InfoIcon.vue')['default'] - const InputIcon: typeof import('primevue/inputicon')['default'] - const InputNumber: typeof import('primevue/inputnumber')['default'] const InputText: typeof import('primevue/inputtext')['default'] - const KeyIcon: typeof import('./src/components/icons/KeyIcon.vue')['default'] const Layout: typeof import('./src/components/icons/Layout.vue')['default'] const LayoutDashboard: typeof import('./src/components/icons/LayoutDashboard.vue')['default'] const LinkIcon: typeof import('./src/components/icons/LinkIcon.vue')['default'] const LockIcon: typeof import('./src/components/icons/LockIcon.vue')['default'] - const LogOutIcon: typeof import('./src/components/icons/LogOutIcon.vue')['default'] const MailIcon: typeof import('./src/components/icons/MailIcon.vue')['default'] const Message: typeof import('primevue/message')['default'] const MonitorIcon: typeof import('./src/components/icons/MonitorIcon.vue')['default'] @@ -162,14 +164,12 @@ declare global { const RouterView: typeof import('vue-router')['RouterView'] const SendIcon: typeof import('./src/components/icons/SendIcon.vue')['default'] const SettingsIcon: typeof import('./src/components/icons/SettingsIcon.vue')['default'] - const ShieldCheckIcon: typeof import('./src/components/icons/ShieldCheckIcon.vue')['default'] const Skeleton: typeof import('primevue/skeleton')['default'] const SlidersIcon: typeof import('./src/components/icons/SlidersIcon.vue')['default'] const StatsCard: typeof import('./src/components/dashboard/StatsCard.vue')['default'] const Tag: typeof import('primevue/tag')['default'] const TelegramIcon: typeof import('./src/components/icons/TelegramIcon.vue')['default'] const TestIcon: typeof import('./src/components/icons/TestIcon.vue')['default'] - const ToggleSwitch: typeof import('primevue/toggleswitch')['default'] const TrashIcon: typeof import('./src/components/icons/TrashIcon.vue')['default'] const Upload: typeof import('./src/components/icons/Upload.vue')['default'] const UploadIcon: typeof import('./src/components/icons/UploadIcon.vue')['default'] diff --git a/src/components/app/AppButton.vue b/src/components/app/AppButton.vue new file mode 100644 index 0000000..c745016 --- /dev/null +++ b/src/components/app/AppButton.vue @@ -0,0 +1,66 @@ + + + diff --git a/src/components/app/AppConfirmHost.vue b/src/components/app/AppConfirmHost.vue new file mode 100644 index 0000000..7469dbd --- /dev/null +++ b/src/components/app/AppConfirmHost.vue @@ -0,0 +1,47 @@ + + + diff --git a/src/components/app/AppDialog.vue b/src/components/app/AppDialog.vue new file mode 100644 index 0000000..edf46ee --- /dev/null +++ b/src/components/app/AppDialog.vue @@ -0,0 +1,97 @@ + + + diff --git a/src/components/app/AppInput.vue b/src/components/app/AppInput.vue new file mode 100644 index 0000000..8a8826a --- /dev/null +++ b/src/components/app/AppInput.vue @@ -0,0 +1,91 @@ + + + diff --git a/src/components/app/AppProgressBar.vue b/src/components/app/AppProgressBar.vue new file mode 100644 index 0000000..e98df07 --- /dev/null +++ b/src/components/app/AppProgressBar.vue @@ -0,0 +1,21 @@ + + + diff --git a/src/components/app/AppSwitch.vue b/src/components/app/AppSwitch.vue new file mode 100644 index 0000000..96ed048 --- /dev/null +++ b/src/components/app/AppSwitch.vue @@ -0,0 +1,46 @@ + + + diff --git a/src/components/app/AppToastHost.vue b/src/components/app/AppToastHost.vue new file mode 100644 index 0000000..a24db05 --- /dev/null +++ b/src/components/app/AppToastHost.vue @@ -0,0 +1,101 @@ + + + diff --git a/src/composables/useAppConfirm.ts b/src/composables/useAppConfirm.ts new file mode 100644 index 0000000..9b5d75b --- /dev/null +++ b/src/composables/useAppConfirm.ts @@ -0,0 +1,86 @@ +import { computed, reactive, readonly } from 'vue'; + +export type AppConfirmOptions = { + message: string; + header?: string; + acceptLabel?: string; + rejectLabel?: string; + accept?: () => void | Promise; + reject?: () => void; +}; + +type AppConfirmState = { + visible: boolean; + loading: boolean; + message: string; + header: string; + acceptLabel: string; + rejectLabel: string; + accept?: () => void | Promise; + reject?: () => void; +}; + +const state = reactive({ + visible: false, + loading: false, + message: '', + header: 'Confirm', + acceptLabel: 'OK', + rejectLabel: 'Cancel', +}); + +const requireConfirm = (options: AppConfirmOptions) => { + state.visible = true; + state.loading = false; + state.message = options.message; + state.header = options.header ?? 'Confirm'; + state.acceptLabel = options.acceptLabel ?? 'OK'; + state.rejectLabel = options.rejectLabel ?? 'Cancel'; + state.accept = options.accept; + state.reject = options.reject; +}; + +const close = () => { + state.visible = false; + state.loading = false; + state.message = ''; + state.accept = undefined; + state.reject = undefined; +}; + +const onReject = () => { + try { + state.reject?.(); + } finally { + close(); + } +}; + +const onAccept = async () => { + state.loading = true; + try { + await state.accept?.(); + close(); + } catch (e) { + // Keep dialog open on error; caller can show a toast. + throw e; + } finally { + state.loading = false; + } +}; + +export const useAppConfirm = () => { + return { + require: requireConfirm, + close, + accept: onAccept, + reject: onReject, + visible: computed(() => state.visible), + loading: computed(() => state.loading), + message: computed(() => state.message), + header: computed(() => state.header), + acceptLabel: computed(() => state.acceptLabel), + rejectLabel: computed(() => state.rejectLabel), + _state: readonly(state), + }; +}; diff --git a/src/composables/useAppToast.ts b/src/composables/useAppToast.ts new file mode 100644 index 0000000..fa6865f --- /dev/null +++ b/src/composables/useAppToast.ts @@ -0,0 +1,64 @@ +import { computed, reactive, readonly } from 'vue'; + +export type AppToastSeverity = 'success' | 'info' | 'warn' | 'warning' | 'error' | 'danger'; + +export type AppToastInput = { + severity?: AppToastSeverity; + summary?: string; + detail?: string; + life?: number; // ms +}; + +export type AppToast = { + id: string; + severity: AppToastSeverity; + summary: string; + detail?: string; + createdAt: number; + life: number; +}; + +const state = reactive<{ toasts: AppToast[] }>({ + toasts: [], +}); + +const normalizeSeverity = (severity?: AppToastSeverity): AppToastSeverity => { + if (!severity) return 'info'; + if (severity === 'warning') return 'warn'; + if (severity === 'danger') return 'error'; + return severity; +}; + +const genId = () => `${Date.now()}-${Math.random().toString(16).slice(2)}`; + +const add = (input: AppToastInput) => { + const toast: AppToast = { + id: genId(), + severity: normalizeSeverity(input.severity), + summary: input.summary ?? '', + detail: input.detail, + createdAt: Date.now(), + life: typeof input.life === 'number' ? input.life : 3000, + }; + state.toasts.push(toast); + return toast.id; +}; + +const remove = (id: string) => { + const idx = state.toasts.findIndex(t => t.id === id); + if (idx !== -1) state.toasts.splice(idx, 1); +}; + +const clear = () => { + state.toasts.splice(0, state.toasts.length); +}; + +export const useAppToast = () => { + return { + add, + remove, + clear, + toasts: computed(() => state.toasts), + _state: readonly(state), + }; +}; diff --git a/src/routes/settings/Settings.vue b/src/routes/settings/Settings.vue index 431fa17..5fa73d3 100644 --- a/src/routes/settings/Settings.vue +++ b/src/routes/settings/Settings.vue @@ -49,6 +49,12 @@
+ + + + + +
@@ -59,6 +65,9 @@ import { computed } from 'vue'; import { useRoute } from 'vue-router'; import PageHeader from '@/components/dashboard/PageHeader.vue'; +import AppConfirmHost from '@/components/app/AppConfirmHost.vue'; +import AppToastHost from '@/components/app/AppToastHost.vue'; +import ClientOnly from '@/components/ClientOnly'; import UserIcon from '@/components/icons/UserIcon.vue'; import GlobeIcon from '@/components/icons/Globe.vue'; import AlertTriangle from '@/components/icons/AlertTriangle.vue'; diff --git a/src/routes/settings/components/ConnectedAccountsCard.vue b/src/routes/settings/components/ConnectedAccountsCard.vue index c4c4492..86f9ac6 100644 --- a/src/routes/settings/components/ConnectedAccountsCard.vue +++ b/src/routes/settings/components/ConnectedAccountsCard.vue @@ -1,15 +1,11 @@ diff --git a/src/routes/settings/pages/AdsVast.vue b/src/routes/settings/pages/AdsVast.vue index 1559ce6..6241ed9 100644 --- a/src/routes/settings/pages/AdsVast.vue +++ b/src/routes/settings/pages/AdsVast.vue @@ -1,15 +1,20 @@