211 lines
5.5 KiB
Vue
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>
|