diff --git a/components/CreatePortalAccountDialog.vue b/components/CreatePortalAccountDialog.vue new file mode 100644 index 0000000..f0e0647 --- /dev/null +++ b/components/CreatePortalAccountDialog.vue @@ -0,0 +1,313 @@ + + + + + mdi-account-plus + Create Portal Account + + + + + + Create a portal account for {{ member?.FullName }} + + + {{ member?.email }} + + + + + + + + The user will receive an email to set up their password and complete registration. + + + + + + + + + + + {{ item.raw.icon }} + + {{ item.raw.title }} + {{ item.raw.description }} + + + + + {{ item.raw.icon }} + {{ item.raw.title }} + + + + + + + + + + {{ selectedGroupInfo?.icon }} + + {{ selectedGroupInfo?.title }} + + {{ selectedGroupInfo?.description }} + + Permissions: + + + {{ permission }} + + + + + + + + + {{ errorMessage }} + + + + + + + + Cancel + + + mdi-account-plus + Create Account + + + + + + + + + diff --git a/pages/auth/setup-password.vue b/pages/auth/setup-password.vue index f7f1a60..fe65036 100644 --- a/pages/auth/setup-password.vue +++ b/pages/auth/setup-password.vue @@ -29,6 +29,22 @@ + + + v === password.value || 'Passwords do not match', ]; +const emailRules = [ + (v: string) => !!v || 'Email address is required', + (v: string) => /.+@.+\..+/.test(v) || 'Please enter a valid email address', +]; + // Set page title with mobile viewport optimization useHead({ title: 'Set Your Password - MonacoUSA Portal', diff --git a/pages/dashboard/member-list.vue b/pages/dashboard/member-list.vue index 096180d..47c60c3 100644 --- a/pages/dashboard/member-list.vue +++ b/pages/dashboard/member-list.vue @@ -205,6 +205,13 @@ @edit="editMember" /> + + + @@ -285,7 +292,9 @@ const showAddDialog = ref(false); const showEditDialog = ref(false); const showViewDialog = ref(false); const showDeleteDialog = ref(false); +const showCreatePortalAccountDialog = ref(false); const selectedMember = ref(null); +const selectedMemberForPortalAccount = ref(null); const deleteLoading = ref(false); // Success handling @@ -512,55 +521,20 @@ const handleMemberUpdated = (updatedMember: Member) => { successMessage.value = `${updatedMember.FullName} has been updated successfully.`; }; -const createPortalAccount = async (member: Member) => { - if (!member.Id || creatingPortalAccountIds.value.includes(member.Id)) return; - - // Add to creating array to show loading state - creatingPortalAccountIds.value.push(member.Id); - - try { - const response = await $fetch(`/api/members/${member.Id}/create-portal-account`, { - method: 'POST' - }); +const createPortalAccount = (member: Member) => { + selectedMemberForPortalAccount.value = member; + showCreatePortalAccountDialog.value = true; +}; - if (response?.success) { - // Update the member in the local array to reflect the new keycloak_id - const index = members.value.findIndex(m => m.Id === member.Id); - if (index !== -1) { - // Get keycloak_id from response.data - members.value[index] = { ...members.value[index], keycloak_id: response.data?.keycloak_id }; - } - - showSuccess.value = true; - successMessage.value = response.message || `Portal account created successfully for ${member.FullName}.`; - } else { - throw new Error(response?.message || 'Failed to create portal account'); - } - } catch (err: any) { - console.error('Error creating portal account:', err); - - // Better error handling - let errorMessage = 'Failed to create portal account. Please try again.'; - if (err.statusCode === 409) { - errorMessage = 'This member already has a portal account or a user with this email already exists.'; - } else if (err.statusCode === 400) { - errorMessage = 'Member must have email, first name, and last name to create a portal account.'; - } else if (err.data?.message) { - errorMessage = err.data.message; - } else if (err.message) { - errorMessage = err.message; - } - - // Show error in snackbar - showSuccess.value = true; // Reuse success snackbar for errors - successMessage.value = errorMessage; - } finally { - // Remove from creating array - const index = creatingPortalAccountIds.value.indexOf(member.Id); - if (index > -1) { - creatingPortalAccountIds.value.splice(index, 1); - } +const handlePortalAccountCreated = (updatedMember: Member) => { + // Update the member in the local array to reflect the new keycloak_id + const index = members.value.findIndex(m => m.Id === updatedMember.Id); + if (index !== -1) { + members.value[index] = updatedMember; } + + showSuccess.value = true; + successMessage.value = `Portal account created successfully for ${updatedMember.FullName}.`; }; // Overdue dues handlers diff --git a/server/api/members/[id]/create-portal-account.post.ts b/server/api/members/[id]/create-portal-account.post.ts index f601982..bbe3aac 100644 --- a/server/api/members/[id]/create-portal-account.post.ts +++ b/server/api/members/[id]/create-portal-account.post.ts @@ -36,6 +36,11 @@ export default defineEventHandler(async (event) => { console.log('[api/members/[id]/create-portal-account.post] Processing member ID:', memberId); + // Get request body for membershipTier + const body = await readBody(event).catch(() => ({})); + const requestedMembershipTier = body.membershipTier; + console.log('[api/members/[id]/create-portal-account.post] Requested membership tier:', requestedMembershipTier); + // 1. Get member data const { getMemberById } = await import('~/server/utils/nocodb'); const member = await getMemberById(memberId); @@ -81,8 +86,8 @@ export default defineEventHandler(async (event) => { }); } - // 5. Determine membership tier based on member data - const membershipTier = determineMembershipTier(member); + // 5. Determine membership tier based on request or member data + const membershipTier = determineMembershipTier(member, requestedMembershipTier); console.log('[api/members/[id]/create-portal-account.post] Determined membership tier:', membershipTier); // 6. Prepare membership data for Keycloak sync @@ -191,7 +196,12 @@ export default defineEventHandler(async (event) => { * Determine membership tier based on member data * This function analyzes member information to assign appropriate portal roles */ -function determineMembershipTier(member: any): 'user' | 'board' | 'admin' { +function determineMembershipTier(member: any, requestedTier?: string): 'user' | 'board' | 'admin' { + // If a valid tier was specifically requested, use that + if (requestedTier && ['user', 'board', 'admin'].includes(requestedTier)) { + console.log(`[determineMembershipTier] Using requested tier: ${requestedTier}`); + return requestedTier as 'user' | 'board' | 'admin'; + } // Use stored portal_group value if available and valid if (member.portal_group && ['user', 'board', 'admin'].includes(member.portal_group)) { console.log(`[determineMembershipTier] Using stored portal_group: ${member.portal_group}`); diff --git a/server/api/members/index.post.ts b/server/api/members/index.post.ts index db6c799..83bd68d 100644 --- a/server/api/members/index.post.ts +++ b/server/api/members/index.post.ts @@ -1,4 +1,4 @@ -import { createMember, handleNocoDbError } from '~/server/utils/nocodb'; +import { createMember, handleNocoDbError, normalizeFieldsFromNocoDB } from '~/server/utils/nocodb'; import { createSessionManager } from '~/server/utils/session'; import { generateMemberID } from '~/server/utils/member-id'; import type { Member, MembershipStatus } from '~/utils/types'; @@ -55,17 +55,32 @@ export default defineEventHandler(async (event) => { console.log('[api/members.post] Sanitized data fields:', Object.keys(memberData)); // Create member in NocoDB - const newMember = await createMember(memberData); + const rawNewMember = await createMember(memberData); - console.log('[api/members.post] ✅ Member created successfully with ID:', newMember.Id); + console.log('[api/members.post] ✅ Member created successfully with ID:', rawNewMember.Id); + + // DIAGNOSTIC: Log raw member data structure from creation + console.log('[api/members.post] DIAGNOSTIC - Raw created member:', JSON.stringify(rawNewMember, null, 2)); + console.log('[api/members.post] DIAGNOSTIC - Raw member fields:', Object.keys(rawNewMember)); - // Return processed member + // Apply field normalization (same as in get members API) + const normalizedMember = normalizeFieldsFromNocoDB(rawNewMember); + console.log('[api/members.post] Applied field normalization'); + console.log('[api/members.post] DIAGNOSTIC - Normalized first_name:', normalizedMember.first_name); + console.log('[api/members.post] DIAGNOSTIC - Normalized last_name:', normalizedMember.last_name); + + // Add computed fields (same as in get members API) + const fullName = `${normalizedMember.first_name || ''} ${normalizedMember.last_name || ''}`.trim(); const processedMember = { - ...newMember, - FullName: `${newMember.first_name || ''} ${newMember.last_name || ''}`.trim(), - FormattedPhone: formatPhoneNumber(newMember.phone) + ...normalizedMember, + FullName: fullName, + FormattedPhone: formatPhoneNumber(normalizedMember.phone) }; + console.log('[api/members.post] DIAGNOSTIC - Final FullName:', `"${processedMember.FullName}"`); + console.log('[api/members.post] DIAGNOSTIC - FullName calculation:', + `"${normalizedMember.first_name || ''}" + " " + "${normalizedMember.last_name || ''}" = "${fullName}"`); + return { success: true, data: processedMember,
+ Create a portal account for {{ member?.FullName }} +
+ {{ member?.email }} +
{{ selectedGroupInfo?.description }}