This commit is contained in:
313
components/CreatePortalAccountDialog.vue
Normal file
313
components/CreatePortalAccountDialog.vue
Normal file
@@ -0,0 +1,313 @@
|
||||
<template>
|
||||
<v-dialog
|
||||
v-model="isOpen"
|
||||
max-width="500"
|
||||
persistent
|
||||
@keydown.esc="cancel"
|
||||
>
|
||||
<v-card class="rounded-lg">
|
||||
<v-card-title class="d-flex align-center">
|
||||
<v-icon color="primary" class="mr-3">mdi-account-plus</v-icon>
|
||||
<span class="text-h6">Create Portal Account</span>
|
||||
</v-card-title>
|
||||
|
||||
<v-card-text class="pb-2">
|
||||
<div class="mb-4">
|
||||
<p class="text-body-1 mb-2">
|
||||
Create a portal account for <strong>{{ member?.FullName }}</strong>
|
||||
</p>
|
||||
<p class="text-body-2 text-medium-emphasis">
|
||||
{{ member?.email }}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="mb-4">
|
||||
<v-alert
|
||||
type="info"
|
||||
variant="tonal"
|
||||
class="mb-4"
|
||||
icon="mdi-information"
|
||||
>
|
||||
<template #text>
|
||||
<div class="text-body-2">
|
||||
The user will receive an email to set up their password and complete registration.
|
||||
</div>
|
||||
</template>
|
||||
</v-alert>
|
||||
</div>
|
||||
|
||||
<v-form ref="formRef" v-model="formValid">
|
||||
<v-select
|
||||
v-model="selectedGroup"
|
||||
:items="groupOptions"
|
||||
label="Assign to Group"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
:rules="groupRules"
|
||||
prepend-inner-icon="mdi-account-group"
|
||||
class="mb-3"
|
||||
>
|
||||
<template #item="{ props, item }">
|
||||
<v-list-item v-bind="props">
|
||||
<template #prepend>
|
||||
<v-icon :color="item.raw.color">{{ item.raw.icon }}</v-icon>
|
||||
</template>
|
||||
<v-list-item-title>{{ item.raw.title }}</v-list-item-title>
|
||||
<v-list-item-subtitle>{{ item.raw.description }}</v-list-item-subtitle>
|
||||
</v-list-item>
|
||||
</template>
|
||||
<template #selection="{ item }">
|
||||
<div class="d-flex align-center">
|
||||
<v-icon :color="item.raw.color" class="mr-2">{{ item.raw.icon }}</v-icon>
|
||||
{{ item.raw.title }}
|
||||
</div>
|
||||
</template>
|
||||
</v-select>
|
||||
|
||||
<!-- Group Description -->
|
||||
<v-card
|
||||
v-if="selectedGroup"
|
||||
variant="tonal"
|
||||
color="primary"
|
||||
class="mb-3"
|
||||
>
|
||||
<v-card-text class="py-3">
|
||||
<div class="d-flex align-center mb-2">
|
||||
<v-icon :color="selectedGroupInfo?.color" class="mr-2">
|
||||
{{ selectedGroupInfo?.icon }}
|
||||
</v-icon>
|
||||
<span class="font-weight-medium">{{ selectedGroupInfo?.title }}</span>
|
||||
</div>
|
||||
<p class="text-body-2 mb-2">{{ selectedGroupInfo?.description }}</p>
|
||||
<div class="text-caption">
|
||||
<strong>Permissions:</strong>
|
||||
<ul class="mt-1 ml-4">
|
||||
<li v-for="permission in selectedGroupInfo?.permissions" :key="permission">
|
||||
{{ permission }}
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
|
||||
<!-- Error Alert -->
|
||||
<v-alert
|
||||
v-if="errorMessage"
|
||||
type="error"
|
||||
variant="tonal"
|
||||
class="mb-3"
|
||||
closable
|
||||
@click:close="errorMessage = ''"
|
||||
>
|
||||
{{ errorMessage }}
|
||||
</v-alert>
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
|
||||
<v-card-actions class="px-6 pb-6">
|
||||
<v-spacer />
|
||||
<v-btn
|
||||
variant="text"
|
||||
@click="cancel"
|
||||
:disabled="loading"
|
||||
>
|
||||
Cancel
|
||||
</v-btn>
|
||||
<v-btn
|
||||
color="primary"
|
||||
variant="elevated"
|
||||
@click="createAccount"
|
||||
:loading="loading"
|
||||
:disabled="!formValid || loading"
|
||||
>
|
||||
<v-icon start>mdi-account-plus</v-icon>
|
||||
Create Account
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { Member } from '~/utils/types';
|
||||
|
||||
interface Props {
|
||||
modelValue: boolean;
|
||||
member: Member | null;
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:modelValue', value: boolean): void;
|
||||
(e: 'account-created', member: Member): void;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
// Reactive state
|
||||
const loading = ref(false);
|
||||
const errorMessage = ref('');
|
||||
const formValid = ref(false);
|
||||
const selectedGroup = ref('user');
|
||||
const formRef = ref();
|
||||
|
||||
// Computed
|
||||
const isOpen = computed({
|
||||
get: () => props.modelValue,
|
||||
set: (value) => emit('update:modelValue', value)
|
||||
});
|
||||
|
||||
// Group options with detailed information
|
||||
const groupOptions = [
|
||||
{
|
||||
title: 'User',
|
||||
value: 'user',
|
||||
description: 'Standard member access',
|
||||
icon: 'mdi-account',
|
||||
color: 'primary',
|
||||
permissions: [
|
||||
'View own profile and update personal information',
|
||||
'View events and RSVP',
|
||||
'Access member directory (if enabled)',
|
||||
'View dues status and payment history'
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Board Member',
|
||||
value: 'board',
|
||||
description: 'Board member privileges',
|
||||
icon: 'mdi-account-tie',
|
||||
color: 'warning',
|
||||
permissions: [
|
||||
'All user permissions',
|
||||
'Create and manage members',
|
||||
'Create and manage events',
|
||||
'View member statistics',
|
||||
'Access board tools and reports'
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Administrator',
|
||||
value: 'admin',
|
||||
description: 'Full system access',
|
||||
icon: 'mdi-shield-crown',
|
||||
color: 'error',
|
||||
permissions: [
|
||||
'All board member permissions',
|
||||
'System configuration and settings',
|
||||
'User and group management',
|
||||
'Delete members and sensitive operations',
|
||||
'Access admin panel and logs'
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
const selectedGroupInfo = computed(() => {
|
||||
return groupOptions.find(group => group.value === selectedGroup.value);
|
||||
});
|
||||
|
||||
// Validation rules
|
||||
const groupRules = [
|
||||
(v: string) => !!v || 'Please select a group'
|
||||
];
|
||||
|
||||
// Methods
|
||||
const cancel = () => {
|
||||
errorMessage.value = '';
|
||||
selectedGroup.value = 'user';
|
||||
isOpen.value = false;
|
||||
};
|
||||
|
||||
const createAccount = async () => {
|
||||
if (!formValid.value || !props.member) return;
|
||||
|
||||
try {
|
||||
loading.value = true;
|
||||
errorMessage.value = '';
|
||||
|
||||
console.log('[CreatePortalAccountDialog] Creating portal account for:', props.member.email, 'Group:', selectedGroup.value);
|
||||
|
||||
const response = await $fetch(`/api/members/${props.member.Id}/create-portal-account`, {
|
||||
method: 'POST',
|
||||
body: {
|
||||
membershipTier: selectedGroup.value
|
||||
}
|
||||
});
|
||||
|
||||
if (response?.success) {
|
||||
console.log('[CreatePortalAccountDialog] Portal account created successfully');
|
||||
|
||||
// Update the member object with the keycloak_id
|
||||
const updatedMember = {
|
||||
...props.member,
|
||||
keycloak_id: response.data?.keycloak_id
|
||||
};
|
||||
|
||||
emit('account-created', updatedMember);
|
||||
isOpen.value = false;
|
||||
|
||||
// Reset form
|
||||
selectedGroup.value = 'user';
|
||||
} else {
|
||||
throw new Error(response?.message || 'Failed to create portal account');
|
||||
}
|
||||
} catch (err: any) {
|
||||
console.error('[CreatePortalAccountDialog] Error creating portal account:', err);
|
||||
|
||||
// Better error handling
|
||||
let message = 'Failed to create portal account. Please try again.';
|
||||
if (err.statusCode === 409) {
|
||||
message = 'This member already has a portal account or a user with this email already exists.';
|
||||
} else if (err.statusCode === 400) {
|
||||
message = 'Member must have email, first name, and last name to create a portal account.';
|
||||
} else if (err.data?.message) {
|
||||
message = err.data.message;
|
||||
} else if (err.message) {
|
||||
message = err.message;
|
||||
}
|
||||
|
||||
errorMessage.value = message;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// Reset form when dialog opens
|
||||
watch(() => props.modelValue, (newValue) => {
|
||||
if (newValue) {
|
||||
selectedGroup.value = 'user';
|
||||
errorMessage.value = '';
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.v-card {
|
||||
border-radius: 12px !important;
|
||||
}
|
||||
|
||||
.v-list-item-subtitle {
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
/* Improve list styling */
|
||||
ul {
|
||||
list-style-type: disc;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
/* Better mobile spacing */
|
||||
@media (max-width: 600px) {
|
||||
.v-card-actions {
|
||||
padding: 16px !important;
|
||||
}
|
||||
|
||||
.v-card-text {
|
||||
padding: 16px !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user