Initial commit
This commit is contained in:
355
src/components/EditAccountDialog.vue
Normal file
355
src/components/EditAccountDialog.vue
Normal file
@@ -0,0 +1,355 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed, watch } from 'vue'
|
||||
import { useServicesStore } from '@MailManager/stores/servicesStore'
|
||||
import { useProvidersStore } from '@MailManager/stores/providersStore'
|
||||
import type { ServiceLocation, ServiceIdentity } from '@MailManager/types'
|
||||
import type { ServiceObject } from '@MailManager/models/service'
|
||||
import ProviderConfigStep from '@MailManager/components/steps/ProviderConfigStep.vue'
|
||||
import ProviderAuthStep from '@MailManager/components/steps/ProviderAuthStep.vue'
|
||||
import TestAndSaveStep from '@MailManager/components/steps/TestAndSaveStep.vue'
|
||||
|
||||
const EDIT_STEPS = {
|
||||
CONFIG: 1,
|
||||
AUTH: 2,
|
||||
TEST: 3
|
||||
} as const
|
||||
|
||||
const props = defineProps<{
|
||||
modelValue: boolean
|
||||
serviceProvider: string
|
||||
serviceIdentifier: string | number
|
||||
}>()
|
||||
|
||||
const emit = defineEmits<{
|
||||
'update:modelValue': [value: boolean]
|
||||
'saved': []
|
||||
}>()
|
||||
|
||||
const servicesStore = useServicesStore()
|
||||
const providersStore = useProvidersStore()
|
||||
|
||||
const dialogOpen = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (val) => emit('update:modelValue', val)
|
||||
})
|
||||
|
||||
const currentStep = ref<number>(EDIT_STEPS.CONFIG)
|
||||
const saving = ref(false)
|
||||
const loading = ref(false)
|
||||
|
||||
// Service data
|
||||
const service = ref<ServiceObject | null>(null)
|
||||
const providerLabel = ref<string>('')
|
||||
|
||||
// Editable fields
|
||||
const accountLabel = ref<string>('')
|
||||
const accountEnabled = ref(true)
|
||||
const configuredLocation = ref<ServiceLocation | null>(null)
|
||||
const configuredIdentity = ref<ServiceIdentity | null>(null)
|
||||
|
||||
// Validation states
|
||||
const configValid = ref(false)
|
||||
const authValid = ref(false)
|
||||
const testAndSaveValid = ref(false)
|
||||
|
||||
// Load service data when dialog opens
|
||||
watch(dialogOpen, async (isOpen) => {
|
||||
if (isOpen) {
|
||||
await loadService()
|
||||
}
|
||||
})
|
||||
|
||||
async function loadService() {
|
||||
loading.value = true
|
||||
try {
|
||||
// Load providers if not already loaded
|
||||
if (!providersStore.has) {
|
||||
await providersStore.list()
|
||||
}
|
||||
|
||||
// Fetch the service
|
||||
service.value = await servicesStore.fetch(props.serviceProvider, props.serviceIdentifier)
|
||||
|
||||
// Set initial values
|
||||
accountLabel.value = service.value.label || ''
|
||||
accountEnabled.value = service.value.enabled
|
||||
configuredLocation.value = service.value.location
|
||||
configuredIdentity.value = service.value.identity
|
||||
|
||||
// Get provider label
|
||||
const provider = providersStore.provider(props.serviceProvider)
|
||||
providerLabel.value = provider?.label || props.serviceProvider
|
||||
|
||||
// Mark config as valid if location exists
|
||||
configValid.value = !!configuredLocation.value
|
||||
authValid.value = !!configuredIdentity.value
|
||||
} catch (error) {
|
||||
console.error('Failed to load service:', error)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
// Stepper configuration
|
||||
const stepperItems = [
|
||||
{ title: 'Protocol', value: EDIT_STEPS.CONFIG },
|
||||
{ title: 'Authentication', value: EDIT_STEPS.AUTH },
|
||||
{ title: 'Test & Save', value: EDIT_STEPS.TEST }
|
||||
]
|
||||
|
||||
const canSave = computed(() => {
|
||||
return testAndSaveValid.value
|
||||
})
|
||||
|
||||
// Navigation button visibility
|
||||
const showPreviousButton = computed(() => currentStep.value > EDIT_STEPS.CONFIG)
|
||||
const showNextButton = computed(() => currentStep.value < EDIT_STEPS.TEST)
|
||||
const showSaveButton = computed(() => currentStep.value === EDIT_STEPS.TEST)
|
||||
|
||||
const canProceedToNext = computed(() => {
|
||||
if (currentStep.value === EDIT_STEPS.CONFIG) {
|
||||
return configValid.value && !!configuredLocation.value
|
||||
}
|
||||
if (currentStep.value === EDIT_STEPS.AUTH) {
|
||||
return authValid.value
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
// Navigation methods
|
||||
function handlePreviousStep() {
|
||||
if (currentStep.value > EDIT_STEPS.CONFIG) {
|
||||
currentStep.value--
|
||||
}
|
||||
}
|
||||
|
||||
function handleNextStep() {
|
||||
if (currentStep.value < EDIT_STEPS.TEST) {
|
||||
currentStep.value++
|
||||
}
|
||||
}
|
||||
|
||||
async function testConnection() {
|
||||
if (!service.value || !configuredLocation.value || !configuredIdentity.value) {
|
||||
return {
|
||||
success: false,
|
||||
message: 'Missing configuration'
|
||||
}
|
||||
}
|
||||
|
||||
const testResult = await servicesStore.test(
|
||||
service.value.provider,
|
||||
service.value.identifier,
|
||||
configuredLocation.value,
|
||||
configuredIdentity.value
|
||||
)
|
||||
|
||||
return testResult
|
||||
}
|
||||
|
||||
async function saveAccount() {
|
||||
if (!service.value || !configuredLocation.value || !configuredIdentity.value) return
|
||||
|
||||
saving.value = true
|
||||
|
||||
try {
|
||||
const accountData = {
|
||||
label: accountLabel.value || service.value.label,
|
||||
enabled: accountEnabled.value,
|
||||
location: configuredLocation.value,
|
||||
identity: configuredIdentity.value
|
||||
}
|
||||
|
||||
await servicesStore.update(
|
||||
service.value.provider,
|
||||
service.value.identifier as string | number,
|
||||
accountData
|
||||
)
|
||||
|
||||
emit('saved')
|
||||
close()
|
||||
} catch (error) {
|
||||
console.error('Failed to save account:', error)
|
||||
// TODO: Show error message to user
|
||||
} finally {
|
||||
saving.value = false
|
||||
}
|
||||
}
|
||||
|
||||
function close() {
|
||||
dialogOpen.value = false
|
||||
// Reset state after animation
|
||||
setTimeout(resetForm, 300)
|
||||
}
|
||||
|
||||
function resetForm() {
|
||||
currentStep.value = EDIT_STEPS.CONFIG
|
||||
service.value = null
|
||||
accountLabel.value = ''
|
||||
accountEnabled.value = true
|
||||
configuredLocation.value = null
|
||||
configuredIdentity.value = null
|
||||
configValid.value = false
|
||||
authValid.value = false
|
||||
testAndSaveValid.value = false
|
||||
}
|
||||
|
||||
// Watch for location changes
|
||||
watch(configuredLocation, (newLocation) => {
|
||||
configValid.value = !!newLocation
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<v-dialog
|
||||
v-model="dialogOpen"
|
||||
max-width="900"
|
||||
persistent
|
||||
scrollable
|
||||
>
|
||||
<v-card>
|
||||
<v-card-title class="d-flex justify-space-between align-center pa-6">
|
||||
<span class="text-h5">Edit Mail Account</span>
|
||||
<v-btn
|
||||
icon="mdi-close"
|
||||
variant="text"
|
||||
@click="close"
|
||||
/>
|
||||
</v-card-title>
|
||||
|
||||
<v-divider />
|
||||
|
||||
<v-card-text v-if="loading" class="text-center py-8">
|
||||
<v-progress-circular indeterminate color="primary" />
|
||||
<p class="text-caption text-medium-emphasis mt-2">Loading account...</p>
|
||||
</v-card-text>
|
||||
|
||||
<v-card-text v-else class="pa-0">
|
||||
<!-- Account Info Header -->
|
||||
<div v-if="service" class="pa-6 bg-surface-variant">
|
||||
<div class="d-flex align-center gap-3">
|
||||
<v-avatar color="primary">
|
||||
<v-icon>mdi-email</v-icon>
|
||||
</v-avatar>
|
||||
<div>
|
||||
<div class="text-subtitle-1 font-weight-medium">
|
||||
{{ service.label || 'Unnamed Account' }}
|
||||
</div>
|
||||
<div class="text-caption text-medium-emphasis">
|
||||
{{ service.primaryAddress || service.identifier }}
|
||||
</div>
|
||||
<div class="text-caption text-medium-emphasis">
|
||||
Provider: {{ providerLabel }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<v-divider />
|
||||
|
||||
<v-stepper
|
||||
v-model="currentStep"
|
||||
:items="stepperItems"
|
||||
alt-labels
|
||||
flat
|
||||
hide-actions
|
||||
>
|
||||
<!-- Step 1: Protocol Configuration -->
|
||||
<template #item.1>
|
||||
<v-card flat class="pa-6">
|
||||
<ProviderConfigStep
|
||||
v-if="service"
|
||||
:provider-id="service.provider"
|
||||
:discovered-location="configuredLocation || undefined"
|
||||
v-model="configuredLocation"
|
||||
@valid="(valid) => configValid = valid"
|
||||
/>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<!-- Step 2: Authentication -->
|
||||
<template #item.2>
|
||||
<v-card flat class="pa-6">
|
||||
<ProviderAuthStep
|
||||
v-if="service"
|
||||
:provider-id="service.provider"
|
||||
:provider-label="providerLabel"
|
||||
:email-address="service.primaryAddress || ''"
|
||||
:discovered-location="configuredLocation || undefined"
|
||||
:prefilled-identity="service.primaryAddress || ''"
|
||||
:prefilled-secret="undefined"
|
||||
v-model="configuredIdentity"
|
||||
@valid="(valid) => authValid = valid"
|
||||
/>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<!-- Step 3: Test & Save -->
|
||||
<template #item.3>
|
||||
<v-card flat class="pa-6">
|
||||
<TestAndSaveStep
|
||||
v-if="service"
|
||||
:provider-id="service.provider"
|
||||
:provider-label="providerLabel"
|
||||
:email-address="service.primaryAddress || ''"
|
||||
:location="configuredLocation"
|
||||
:identity="configuredIdentity"
|
||||
:prefilled-label="accountLabel"
|
||||
:on-test="testConnection"
|
||||
@update:label="(val) => accountLabel = val"
|
||||
@update:enabled="(val) => accountEnabled = val"
|
||||
@valid="(valid) => testAndSaveValid = valid"
|
||||
/>
|
||||
</v-card>
|
||||
</template>
|
||||
</v-stepper>
|
||||
</v-card-text>
|
||||
|
||||
<v-divider />
|
||||
|
||||
<v-card-actions class="pa-6">
|
||||
<!-- Previous Button -->
|
||||
<v-btn
|
||||
v-if="showPreviousButton"
|
||||
variant="text"
|
||||
prepend-icon="mdi-arrow-left"
|
||||
@click="handlePreviousStep"
|
||||
>
|
||||
Previous
|
||||
</v-btn>
|
||||
|
||||
<v-spacer />
|
||||
|
||||
<v-btn
|
||||
variant="text"
|
||||
@click="close"
|
||||
>
|
||||
Cancel
|
||||
</v-btn>
|
||||
|
||||
<!-- Next Button -->
|
||||
<v-btn
|
||||
v-if="showNextButton"
|
||||
color="primary"
|
||||
append-icon="mdi-arrow-right"
|
||||
:disabled="!canProceedToNext"
|
||||
@click="handleNextStep"
|
||||
>
|
||||
Next
|
||||
</v-btn>
|
||||
|
||||
<!-- Save Button -->
|
||||
<v-btn
|
||||
v-if="showSaveButton"
|
||||
color="primary"
|
||||
:loading="saving"
|
||||
:disabled="!canSave"
|
||||
@click="saveAccount"
|
||||
>
|
||||
<v-icon start>mdi-content-save</v-icon>
|
||||
Save Changes
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
Reference in New Issue
Block a user