Files
user_manager/src/views/AccountPanel.vue
2026-02-10 20:36:49 -05:00

211 lines
5.5 KiB
Vue

<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>