Initial commit
This commit is contained in:
375
src/pages/AccountsPage.vue
Normal file
375
src/pages/AccountsPage.vue
Normal file
@@ -0,0 +1,375 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, onMounted } from 'vue'
|
||||
import { useServicesStore } from '@/stores/servicesStore'
|
||||
import AddAccountDialog from '@/components/AddAccountDialog.vue'
|
||||
import type { ServiceObject } from '@/models'
|
||||
|
||||
const servicesStore = useServicesStore()
|
||||
|
||||
const showAddDialog = ref(false)
|
||||
const showEditDialog = ref(false)
|
||||
const showDeleteConfirm = ref(false)
|
||||
const showTestResult = ref(false)
|
||||
const selectedAccount = ref<any>({})
|
||||
const loading = ref(false)
|
||||
const saving = ref(false)
|
||||
const deleting = ref(false)
|
||||
const testingId = ref<string | null>(null)
|
||||
const testResult = ref<any>(null)
|
||||
|
||||
const groupedServices = computed(() => servicesStore.servicesByProvider)
|
||||
|
||||
const hasAccounts = computed(() => servicesStore.has)
|
||||
|
||||
onMounted(async () => {
|
||||
loading.value = true
|
||||
try {
|
||||
await servicesStore.list()
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
})
|
||||
|
||||
function getProviderIcon(providerId: string): string {
|
||||
const icons: Record<string, string> = {
|
||||
'smtp': 'mdi-email-multiple',
|
||||
'jmap': 'mdi-api',
|
||||
'exchange': 'mdi-microsoft',
|
||||
}
|
||||
return icons[providerId] || 'mdi-email'
|
||||
}
|
||||
|
||||
function getProviderLabel(providerId: string): string {
|
||||
const labels: Record<string, string> = {
|
||||
'smtp': 'SMTP/IMAP',
|
||||
'jmap': 'JMAP',
|
||||
'exchange': 'Microsoft Exchange',
|
||||
}
|
||||
return labels[providerId] || providerId.toUpperCase()
|
||||
}
|
||||
|
||||
function editAccount(account: any) {
|
||||
selectedAccount.value = { ...account }
|
||||
showEditDialog.value = true
|
||||
}
|
||||
|
||||
async function saveEdit() {
|
||||
saving.value = true
|
||||
try {
|
||||
await servicesStore.update(
|
||||
selectedAccount.value.provider,
|
||||
selectedAccount.value.identifier,
|
||||
selectedAccount.value
|
||||
)
|
||||
showEditDialog.value = false
|
||||
} catch (error) {
|
||||
console.error('Failed to update account:', error)
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function confirmDelete(account: any) {
|
||||
selectedAccount.value = account
|
||||
showDeleteConfirm.value = true
|
||||
}
|
||||
|
||||
async function deleteAccount() {
|
||||
deleting.value = true
|
||||
try {
|
||||
await servicesStore.delete(
|
||||
selectedAccount.value.provider,
|
||||
selectedAccount.value.identifier
|
||||
)
|
||||
showDeleteConfirm.value = false
|
||||
selectedAccount.value = {}
|
||||
} catch (error) {
|
||||
console.error('Failed to delete account:', error)
|
||||
} finally {
|
||||
deleting.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function testAccount(service: ServiceObject) {
|
||||
try {
|
||||
const result = await servicesStore.test(
|
||||
service.provider,
|
||||
service.identifier,
|
||||
service.location,
|
||||
service.identity
|
||||
)
|
||||
testResult.value = result
|
||||
showTestResult.value = true
|
||||
} catch (error: any) {
|
||||
testResult.value = {
|
||||
success: false,
|
||||
message: error.message || 'Connection test failed'
|
||||
}
|
||||
showTestResult.value = true
|
||||
} finally {
|
||||
testingId.value = null
|
||||
}
|
||||
}
|
||||
|
||||
async function handleAccountSaved() {
|
||||
await servicesStore.list()
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-container fluid>
|
||||
<!-- Page Header -->
|
||||
<div class="d-flex align-center justify-space-between mb-6">
|
||||
<div>
|
||||
<h1 class="text-h4 mb-1">Mail Accounts</h1>
|
||||
<p class="text-body-2 text-medium-emphasis">
|
||||
Manage your email accounts across all providers
|
||||
</p>
|
||||
</div>
|
||||
<v-btn
|
||||
color="primary"
|
||||
prepend-icon="mdi-plus"
|
||||
@click="showAddDialog = true"
|
||||
>
|
||||
Add Account
|
||||
</v-btn>
|
||||
</div>
|
||||
|
||||
<!-- Loading State -->
|
||||
<v-row v-if="loading" class="mt-8">
|
||||
<v-col
|
||||
v-for="i in 3"
|
||||
:key="i"
|
||||
cols="12"
|
||||
md="6"
|
||||
lg="4"
|
||||
>
|
||||
<v-skeleton-loader type="card" />
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<!-- Empty State -->
|
||||
<v-card
|
||||
v-else-if="!hasAccounts"
|
||||
class="text-center pa-12"
|
||||
variant="flat"
|
||||
>
|
||||
<v-icon
|
||||
size="80"
|
||||
color="grey-lighten-1"
|
||||
class="mb-4"
|
||||
>
|
||||
mdi-email-off-outline
|
||||
</v-icon>
|
||||
<h2 class="text-h5 mb-2">No Mail Accounts</h2>
|
||||
<p class="text-body-1 text-medium-emphasis mb-6">
|
||||
Add your first mail account to start sending and receiving emails
|
||||
</p>
|
||||
<v-btn
|
||||
color="primary"
|
||||
size="large"
|
||||
prepend-icon="mdi-plus"
|
||||
@click="showAddDialog = true"
|
||||
>
|
||||
Add Your First Account
|
||||
</v-btn>
|
||||
</v-card>
|
||||
|
||||
<!-- Accounts by Provider -->
|
||||
<template v-else>
|
||||
<div
|
||||
v-for="(services, providerId) in groupedServices"
|
||||
:key="providerId"
|
||||
class="mb-8"
|
||||
>
|
||||
<!-- Provider Header -->
|
||||
<div class="d-flex align-center mb-4">
|
||||
<v-icon
|
||||
:icon="getProviderIcon(providerId)"
|
||||
class="mr-2"
|
||||
/>
|
||||
<h2 class="text-h6">
|
||||
{{ getProviderLabel(providerId) }}
|
||||
</h2>
|
||||
<v-chip
|
||||
size="small"
|
||||
class="ml-2"
|
||||
variant="text"
|
||||
>
|
||||
{{ services.length }} account{{ services.length !== 1 ? 's' : '' }}
|
||||
</v-chip>
|
||||
</div>
|
||||
|
||||
<!-- Service Cards -->
|
||||
<v-row>
|
||||
<v-col
|
||||
v-for="service in services"
|
||||
:key="`${service.provider}:${service.identifier}`"
|
||||
cols="12"
|
||||
md="6"
|
||||
lg="4"
|
||||
>
|
||||
<v-card
|
||||
:class="{ 'border-error': !service.enabled }"
|
||||
variant="outlined"
|
||||
hover
|
||||
>
|
||||
<v-card-text>
|
||||
<div class="d-flex align-center justify-space-between mb-2">
|
||||
<div class="d-flex align-center">
|
||||
<v-avatar
|
||||
size="40"
|
||||
:color="service.enabled ? 'primary' : 'grey'"
|
||||
class="mr-3"
|
||||
>
|
||||
<v-icon color="white">
|
||||
{{ service.enabled ? 'mdi-email' : 'mdi-email-off' }}
|
||||
</v-icon>
|
||||
</v-avatar>
|
||||
<div>
|
||||
<h3 class="text-h6">{{ service.label }}</h3>
|
||||
<p class="text-caption text-medium-emphasis">
|
||||
{{ service.primaryAddress || (service.identity?.type === 'BA' ? service.identity.identity : 'No email configured') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<v-chip
|
||||
:color="service.enabled ? 'success' : 'error'"
|
||||
size="small"
|
||||
variant="flat"
|
||||
>
|
||||
{{ service.enabled ? 'Enabled' : 'Disabled' }}
|
||||
</v-chip>
|
||||
</div>
|
||||
|
||||
<!-- Service Stats/Info -->
|
||||
<v-divider class="my-3" />
|
||||
<div class="text-caption text-medium-emphasis">
|
||||
<div class="d-flex align-center mb-1">
|
||||
<v-icon size="small" class="mr-1">mdi-identifier</v-icon>
|
||||
ID: {{ service.identifier }}
|
||||
</div>
|
||||
</div>
|
||||
</v-card-text>
|
||||
|
||||
<v-card-actions>
|
||||
<v-btn
|
||||
variant="text"
|
||||
size="small"
|
||||
prepend-icon="mdi-pencil"
|
||||
@click="editAccount(service)"
|
||||
>
|
||||
Edit
|
||||
</v-btn>
|
||||
<v-btn
|
||||
variant="text"
|
||||
size="small"
|
||||
prepend-icon="mdi-connection"
|
||||
:loading="testingId === service.identifier"
|
||||
@click="testAccount(service)"
|
||||
>
|
||||
Test
|
||||
</v-btn>
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
variant="text"
|
||||
size="small"
|
||||
color="error"
|
||||
icon="mdi-delete"
|
||||
@click="confirmDelete(service)"
|
||||
/>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Add Account Dialog -->
|
||||
<AddAccountDialog
|
||||
v-model="showAddDialog"
|
||||
@saved="handleAccountSaved"
|
||||
/>
|
||||
|
||||
<!-- Edit Account Dialog -->
|
||||
<v-dialog
|
||||
v-model="showEditDialog"
|
||||
max-width="600"
|
||||
>
|
||||
<v-card>
|
||||
<v-card-title>Edit Account</v-card-title>
|
||||
<v-card-text>
|
||||
<v-text-field
|
||||
v-model="selectedAccount.label"
|
||||
label="Account Name"
|
||||
variant="outlined"
|
||||
/>
|
||||
<v-switch
|
||||
v-model="selectedAccount.enabled"
|
||||
label="Enable this account"
|
||||
color="primary"
|
||||
/>
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
variant="text"
|
||||
@click="showEditDialog = false"
|
||||
>
|
||||
Cancel
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="primary"
|
||||
:loading="saving"
|
||||
@click="saveEdit"
|
||||
>
|
||||
Save
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- Delete Confirmation Dialog -->
|
||||
<v-dialog
|
||||
v-model="showDeleteConfirm"
|
||||
max-width="400"
|
||||
>
|
||||
<v-card>
|
||||
<v-card-title class="text-h6">Delete Account?</v-card-title>
|
||||
<v-card-text>
|
||||
Are you sure you want to delete <strong>{{ selectedAccount?.label }}</strong>?
|
||||
This action cannot be undone.
|
||||
</v-card-text>
|
||||
<v-card-actions>
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
variant="text"
|
||||
@click="showDeleteConfirm = false"
|
||||
>
|
||||
Cancel
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="error"
|
||||
variant="flat"
|
||||
:loading="deleting"
|
||||
@click="deleteAccount"
|
||||
>
|
||||
Delete
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
|
||||
<!-- Test Result Snackbar -->
|
||||
<v-snackbar
|
||||
v-model="showTestResult"
|
||||
:color="testResult?.success ? 'success' : 'error'"
|
||||
:timeout="5000"
|
||||
>
|
||||
<v-icon start>
|
||||
{{ testResult?.success ? 'mdi-check-circle' : 'mdi-alert-circle' }}
|
||||
</v-icon>
|
||||
{{ testResult?.message || 'Connection test completed' }}
|
||||
</v-snackbar>
|
||||
</v-container>
|
||||
</template>
|
||||
Reference in New Issue
Block a user