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 @@
+
+
+
+ !v && confirm.close()"
+ :title="confirm.header.value"
+ maxWidthClass="max-w-md"
+ >
+
+
+
+ {{ confirm.message.value }}
+
+
+
+
+
+
+ {{ confirm.rejectLabel.value }}
+
+
+ {{ confirm.acceptLabel.value }}
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ title }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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 @@
+
+
+
+
+
+
+
+
+
{{ t.summary }}
+
{{ t.detail }}
+
+
+
+
+
+
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 @@
@@ -104,7 +92,7 @@ const items = [
-
-
+
+ Change Password
+
-
+
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 @@