feat(settings): add Billing, Danger Zone, Domains DNS, Notification, Player, and Security settings pages

- Implemented Billing page with wallet balance, current plan, usage stats, available plans, and payment history.
- Created Danger Zone page for account deletion and data clearing actions with confirmation prompts.
- Developed Domains DNS page for managing whitelisted domains for iframe embedding, including add and remove functionality.
- Added Notification Settings page to configure email, push, marketing, and Telegram notifications.
- Introduced Player Settings page to customize video player behavior such as autoplay, loop, and controls visibility.
- Established Security and Connected Accounts page for managing user profile, two-factor authentication, and connected accounts.
This commit is contained in:
2026-03-01 22:49:30 +07:00
parent c6924afe5b
commit cd9aab8979
65 changed files with 3150 additions and 1133 deletions

View File

@@ -0,0 +1,168 @@
<template>
<section>
<PageHeader
:title="content[route.name as keyof typeof content]?.title || 'Settings'"
:description="content[route.name as keyof typeof content]?.subtitle || 'Manage your account settings and preferences.'"
:breadcrumbs="breadcrumbs"
/>
<div class="max-w-7xl mx-auto pb-12">
<div class="flex flex-col md:flex-row gap-8 mt-6">
<!-- Sidebar Navigation (GitHub-style) -->
<aside class="md:w-56 shrink-0">
<div class="flex items-center gap-4 mb-8">
<div class="w-16 h-16 rounded-full bg-primary/10 flex items-center justify-center shrink-0">
<UserIcon class="w-8 h-8 text-primary" :filled="true" />
</div>
<div>
<h3 class="text-lg font-semibold text-foreground">{{ auth.user?.username || 'User' }}</h3>
<p class="text-sm text-foreground/60">{{ auth.user?.email || '' }}</p>
</div>
</div>
<nav class="space-y-6">
<div v-for="section in menuSections" :key="section.title">
<h3 v-if="section.title" class="text-xs font-semibold text-foreground/50 uppercase tracking-wider mb-2 pl-3">
{{ section.title }}
</h3>
<ul class="space-y-0.5">
<li v-for="item in section.items" :key="item.value">
<router-link
:to="tabPaths[item.value]"
:class="[
'w-full flex items-center gap-3 px-3 py-2 rounded-md text-sm font-medium transition-all duration-150',
currentTab === item.value
? 'bg-primary/10 text-primary font-semibold'
: item.danger
? 'text-danger hover:bg-danger/10'
: 'text-foreground/70 hover:bg-muted hover:text-foreground'
]"
>
<component :is="item.icon" class="w-5 h-5 shrink-0" :filled="currentTab === item.value" />
{{ item.label }}
</router-link>
</li>
</ul>
</div>
</nav>
</aside>
<!-- Main Content Area -->
<main class="flex-1 min-w-0">
<router-view />
</main>
</div>
</div>
</section>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import { useRoute } from 'vue-router';
import PageHeader from '@/components/dashboard/PageHeader.vue';
import UserIcon from '@/components/icons/UserIcon.vue';
import GlobeIcon from '@/components/icons/Globe.vue';
import ActivityIcon from '@/components/icons/ActivityIcon.vue';
import AlertTriangle from '@/components/icons/AlertTriangle.vue';
import { useAuthStore } from '@/stores/auth';
import CreditCardIcon from '@/components/icons/CreditCardIcon.vue';
import Bell from '@/components/icons/Bell.vue';
import VideoIcon from '@/components/icons/VideoIcon.vue';
import AdvertisementIcon from '@/components/icons/AdvertisementIcon.vue';
import VideoPlayIcon from '@/components/icons/VideoPlayIcon.vue';
const route = useRoute();
const auth = useAuthStore();
// Map tab values to their paths
const tabPaths: Record<string, string> = {
profile: '/settings',
security: '/settings/security',
notifications: '/settings/notifications',
player: '/settings/player',
billing: '/settings/billing',
domains: '/settings/domains',
ads: '/settings/ads',
danger: '/settings/danger',
};
// Menu items grouped by category (GitHub-style)
const menuSections: { title?: string; items: { value: string; label: string; icon: any; danger?: boolean }[] }[] = [
{
title: 'Security',
items: [
{ value: 'security', label: 'Security', icon: UserIcon },
{ value: 'billing', label: 'Billing & Plans', icon: CreditCardIcon },
],
},
{
title: 'Preferences',
items: [
{ value: 'notifications', label: 'Notifications', icon: Bell },
{ value: 'player', label: 'Player', icon: VideoPlayIcon },
],
},
{
title: 'Integrations',
items: [
{ value: 'domains', label: 'Allowed Domains', icon: GlobeIcon },
{ value: 'ads', label: 'Ads & VAST', icon: AdvertisementIcon },
],
},
{
title: 'Danger Zone',
items: [
{ value: 'danger', label: 'Danger Zone', icon: AlertTriangle, danger: true },
],
},
] as const;
type TabValue = typeof menuSections[number]['items'][number]['value'];
// Get current tab from route path
const currentTab = computed<TabValue>(() => {
const path = route.path as string;
const tabName = path.replace('/settings', '') || '/profile';
if (tabName === '' || tabName === '/') return 'profile';
return (tabName.replace('/', '') as TabValue) || 'profile';
});
// Breadcrumbs with dynamic tab
const allMenuItems = menuSections.flatMap(section => section.items);
const currentItem = allMenuItems.find(item => item.value === currentTab.value);
const breadcrumbs = [
{ label: 'Dashboard', to: '/overview' },
{ label: 'Settings', to: '/settings' },
...(currentItem ? [{ label: currentItem.label }] : []),
];
const content = {
security: {
title: 'Security & Connected Apps',
subtitle: 'Manage your security settings and connected applications.'
},
notifications: {
title: 'Notifications',
subtitle: 'Choose how you want to receive notifications and updates.'
},
player: {
title: 'Player Settings',
subtitle: 'Configure default video player behavior and features.'
},
billing: {
title: 'Billing & Plans',
subtitle: 'Your current subscription and billing information.'
},
domains: {
title: 'Allowed Domains',
subtitle: 'Add domains to your whitelist to allow embedding content via iframe.'
},
ads: {
title: 'Ads & VAST',
subtitle: 'Create and manage VAST ad templates for your videos.'
},
danger: {
title: 'Danger Zone',
subtitle: 'Irreversible and destructive actions. Be careful!'
}
}
</script>