Initial commit
This commit is contained in:
210
src/views/AccountPanel.vue
Normal file
210
src/views/AccountPanel.vue
Normal file
@@ -0,0 +1,210 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, computed } from 'vue';
|
||||
import { userManagerService } from '@/services/userManagerService';
|
||||
import type { User } from '@/types';
|
||||
|
||||
const props = defineProps<{
|
||||
user: User;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
update: [];
|
||||
}>();
|
||||
|
||||
const loading = ref(false);
|
||||
const error = ref<string | null>(null);
|
||||
const success = ref<string | null>(null);
|
||||
|
||||
const formData = ref({
|
||||
label: props.user.label,
|
||||
identity: props.user.identity,
|
||||
enabled: props.user.enabled,
|
||||
profile: { ...(typeof props.user.profile === 'object' && !Array.isArray(props.user.profile) && props.user.profile ? props.user.profile : {}) }
|
||||
});
|
||||
|
||||
const normalizedProfile = computed<Record<string, any>>(() => {
|
||||
const profile: any = props.user.profile;
|
||||
if (!profile || Array.isArray(profile) || typeof profile !== 'object') return {};
|
||||
return profile;
|
||||
});
|
||||
|
||||
const isLocalAccount = computed(() => !props.user.provider);
|
||||
|
||||
// Standard profile fields that should always be shown
|
||||
const standardFields = ['email', 'phone', 'first_name', 'last_name'];
|
||||
|
||||
const profileFields = computed(() => {
|
||||
const profile = normalizedProfile.value;
|
||||
const managed = new Set<string>((props.user.provider_managed_fields ?? []) as string[]);
|
||||
|
||||
// Always show standard fields, plus any additional fields from the profile
|
||||
const allKeys = new Set([...standardFields, ...Object.keys(profile)]);
|
||||
|
||||
return Array.from(allKeys).map((key) => ({
|
||||
key,
|
||||
value: profile[key] ?? '',
|
||||
// Local accounts: all fields editable
|
||||
// External accounts: field is editable only if NOT in provider_managed_fields
|
||||
editable: isLocalAccount.value ? true : !managed.has(key),
|
||||
provider: managed.has(key) ? props.user.provider : null
|
||||
}));
|
||||
});
|
||||
|
||||
const hasLockedFields = computed(() => {
|
||||
return profileFields.value.some(f => !f.editable);
|
||||
});
|
||||
|
||||
const saveChanges = async () => {
|
||||
loading.value = true;
|
||||
error.value = null;
|
||||
success.value = null;
|
||||
|
||||
try {
|
||||
// Build profile updates (only editable fields)
|
||||
const profileUpdates: Record<string, any> = {};
|
||||
profileFields.value.forEach(field => {
|
||||
if (field.editable && formData.value.profile[field.key] !== undefined) {
|
||||
profileUpdates[field.key] = formData.value.profile[field.key];
|
||||
}
|
||||
});
|
||||
|
||||
await userManagerService.updateUser(props.user.uid, {
|
||||
label: formData.value.label,
|
||||
enabled: formData.value.enabled,
|
||||
profile: profileUpdates
|
||||
});
|
||||
|
||||
success.value = 'User profile updated successfully';
|
||||
emit('update');
|
||||
} catch (err: any) {
|
||||
error.value = err.message || 'Failed to update user';
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<VForm @submit.prevent="saveChanges">
|
||||
<VAlert
|
||||
v-if="error"
|
||||
type="error"
|
||||
closable
|
||||
class="mb-4"
|
||||
@click:close="error = null"
|
||||
>
|
||||
{{ error }}
|
||||
</VAlert>
|
||||
|
||||
<VAlert
|
||||
v-if="success"
|
||||
type="success"
|
||||
closable
|
||||
class="mb-4"
|
||||
@click:close="success = null"
|
||||
>
|
||||
{{ success }}
|
||||
</VAlert>
|
||||
|
||||
<div class="profile-form-container">
|
||||
|
||||
<!-- Basic Info -->
|
||||
<VRow>
|
||||
<VCol cols="12" md="6">
|
||||
<VTextField
|
||||
v-model="formData.label"
|
||||
label="Display Name"
|
||||
prepend-inner-icon="mdi-account"
|
||||
variant="outlined"
|
||||
required
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12" md="6">
|
||||
<VTextField
|
||||
v-model="formData.identity"
|
||||
label="Identity (Email/Username)"
|
||||
variant="outlined"
|
||||
disabled
|
||||
hint="Identity cannot be changed"
|
||||
persistent-hint
|
||||
/>
|
||||
</VCol>
|
||||
|
||||
<VCol cols="12" md="6">
|
||||
<VSwitch
|
||||
v-model="formData.enabled"
|
||||
label="Account Enabled"
|
||||
color="primary"
|
||||
hide-details
|
||||
/>
|
||||
</VCol>
|
||||
</VRow>
|
||||
|
||||
<VDivider class="my-6" />
|
||||
|
||||
<!-- Provider Info -->
|
||||
<VRow v-if="user.provider">
|
||||
<VCol cols="12">
|
||||
<VAlert type="info" icon="mdi-shield-key">
|
||||
<div class="font-weight-medium mb-2">
|
||||
External Provider: {{ user.provider.toUpperCase() }}
|
||||
</div>
|
||||
<div class="text-caption">
|
||||
Fields marked with <VIcon icon="mdi-shield-key" size="x-small" class="mx-1" /> are managed by the external identity provider and cannot be edited here.
|
||||
</div>
|
||||
</VAlert>
|
||||
</VCol>
|
||||
</VRow>
|
||||
|
||||
<!-- Profile Fields -->
|
||||
<VRow>
|
||||
<VCol cols="12">
|
||||
<div class="text-h6 mb-4">Profile Information</div>
|
||||
</VCol>
|
||||
|
||||
<VCol
|
||||
v-for="field in profileFields"
|
||||
:key="field.key"
|
||||
cols="12"
|
||||
md="6"
|
||||
>
|
||||
<VTextField
|
||||
v-model="formData.profile[field.key]"
|
||||
:label="field.key.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())"
|
||||
variant="outlined"
|
||||
:disabled="!field.editable"
|
||||
:hint="field.editable ? '' : `Managed by ${field.provider}`"
|
||||
:persistent-hint="!field.editable"
|
||||
>
|
||||
<template v-if="!field.editable" #append-inner>
|
||||
<VIcon icon="mdi-shield-key" size="small" color="warning" />
|
||||
</template>
|
||||
</VTextField>
|
||||
</VCol>
|
||||
</VRow>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Actions -->
|
||||
<VRow class="mt-4">
|
||||
<VCol cols="12" class="d-flex justify-end">
|
||||
<VBtn
|
||||
type="submit"
|
||||
color="primary"
|
||||
:loading="loading"
|
||||
prepend-icon="mdi-content-save"
|
||||
>
|
||||
Save Changes
|
||||
</VBtn>
|
||||
</VCol>
|
||||
</VRow>
|
||||
</VForm>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
.profile-form-container {
|
||||
max-height: 600px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user