diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 509b4d4..d024dd8 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -34,7 +34,13 @@ "mcp__playwright__browser_evaluate", "mcp__playwright__browser_hover", "mcp__playwright__browser_resize", - "mcp__playwright__browser_console_messages" + "mcp__playwright__browser_console_messages", + "mcp__serena__check_onboarding_performed", + "mcp__serena__get_symbols_overview", + "mcp__serena__find_referencing_symbols", + "mcp__zen__thinkdeep", + "mcp__serena__insert_after_symbol", + "mcp__serena__replace_symbol_body" ], "deny": [], "ask": [] diff --git a/.serena/cache/typescript/document_symbols_cache_v23-06-25.pkl b/.serena/cache/typescript/document_symbols_cache_v23-06-25.pkl index 90bbfe6..3ae6cf1 100644 Binary files a/.serena/cache/typescript/document_symbols_cache_v23-06-25.pkl and b/.serena/cache/typescript/document_symbols_cache_v23-06-25.pkl differ diff --git a/pages/admin/members/index.vue b/pages/admin/members/index.vue index 731443e..6dc585b 100644 --- a/pages/admin/members/index.vue +++ b/pages/admin/members/index.vue @@ -339,10 +339,10 @@ const membershipFilter = ref(null); // Stats const stats = ref({ - total: 156, - active: 142, - newThisMonth: 8, - renewalDue: 23 + total: 0, + active: 0, + newThisMonth: 0, + renewalDue: 0 }); // Form data @@ -368,42 +368,8 @@ const headers = [ { title: 'Actions', key: 'actions', sortable: false, align: 'end' } ]; -// Mock data -const members = ref([ - { - member_id: '1', - first_name: 'John', - last_name: 'Smith', - email: 'john.smith@example.com', - membership_type: 'Premium', - status: 'active', - dues_status: 'Paid', - join_date: '2023-01-15', - phone: '555-0100' - }, - { - member_id: '2', - first_name: 'Sarah', - last_name: 'Johnson', - email: 'sarah.j@example.com', - membership_type: 'Standard', - status: 'active', - dues_status: 'Due', - join_date: '2023-03-22', - phone: '555-0101' - }, - { - member_id: '3', - first_name: 'Michael', - last_name: 'Williams', - email: 'michael.w@example.com', - membership_type: 'VIP', - status: 'active', - dues_status: 'Paid', - join_date: '2022-11-08', - phone: '555-0102' - } -]); +// Real data from API +const members = ref([]); // Computed const filteredMembers = computed(() => { @@ -496,12 +462,51 @@ const saveMember = () => { showCreateDialog.value = false; }; +// Load real members data from API +const loadMembers = async () => { + loading.value = true; + try { + // Fetch members from API + const { data } = await $fetch('/api/members'); + + if (data?.members) { + // Transform the data to match our interface + members.value = data.members.map((member: any) => ({ + member_id: member.Id || member.id, + first_name: member.first_name, + last_name: member.last_name, + email: member.email, + membership_type: member.membership_type || 'Standard', + status: member.membership_status === 'Active' ? 'active' : 'inactive', + dues_status: member.dues_status || 'Unknown', + join_date: member.member_since || member.created_at, + phone: member.phone_number || member.phone || '' + })); + + // Calculate stats from real data + const now = new Date(); + const startOfMonth = new Date(now.getFullYear(), now.getMonth(), 1); + + stats.value = { + total: members.value.length, + active: members.value.filter(m => m.status === 'active').length, + newThisMonth: members.value.filter(m => { + const joinDate = new Date(m.join_date); + return joinDate >= startOfMonth; + }).length, + renewalDue: members.value.filter(m => m.dues_status === 'Due' || m.dues_status === 'Overdue').length + }; + } + } catch (error) { + console.error('Error loading members:', error); + // Keep empty array if load fails + } finally { + loading.value = false; + } +}; + // Load data on mount onMounted(async () => { - loading.value = true; - // Fetch members from API - setTimeout(() => { - loading.value = false; - }, 1000); + await loadMembers(); }); \ No newline at end of file diff --git a/pages/admin/payments/index.vue b/pages/admin/payments/index.vue index 695495e..6b0f803 100644 --- a/pages/admin/payments/index.vue +++ b/pages/admin/payments/index.vue @@ -38,7 +38,7 @@
-
${{ stats.pending.toLocaleString() }}
+
${{ stats.pendingPayments.toLocaleString() }}
Pending
mdi-clock-outline @@ -51,8 +51,8 @@
-
${{ stats.overdue.toLocaleString() }}
-
Overdue
+
{{ stats.failedTransactions }}
+
Failed
mdi-alert-circle-outline
@@ -64,8 +64,8 @@
-
{{ stats.transactions }}
-
Transactions
+
{{ stats.successfulTransactions }}
+
Successful
mdi-swap-horizontal
@@ -349,10 +349,10 @@ const dateTo = ref(''); // Stats const stats = ref({ - totalRevenue: 45820, - pending: 3250, - overdue: 1800, - transactions: 342 + totalRevenue: 0, + pendingPayments: 0, + successfulTransactions: 0, + failedTransactions: 0 }); // Form data @@ -387,53 +387,8 @@ const headers = [ { title: 'Actions', key: 'actions', sortable: false, align: 'end' } ]; -// Mock data -const payments = ref([ - { - id: 1, - transaction_id: 'TXN-2024-001', - member_name: 'John Smith', - member_email: 'john.smith@example.com', - amount: 500, - type: 'Membership', - status: 'Completed', - date: new Date('2024-01-15'), - method: 'Credit Card' - }, - { - id: 2, - transaction_id: 'TXN-2024-002', - member_name: 'Sarah Johnson', - member_email: 'sarah.j@example.com', - amount: 250, - type: 'Event', - status: 'Pending', - date: new Date('2024-01-14'), - method: 'Bank Transfer' - }, - { - id: 3, - transaction_id: 'TXN-2024-003', - member_name: 'Michael Williams', - member_email: 'michael.w@example.com', - amount: 1000, - type: 'Donation', - status: 'Completed', - date: new Date('2024-01-13'), - method: 'Check' - }, - { - id: 4, - transaction_id: 'TXN-2024-004', - member_name: 'Emma Davis', - member_email: 'emma.d@example.com', - amount: 75, - type: 'Event', - status: 'Failed', - date: new Date('2024-01-12'), - method: 'Credit Card' - } -]); +// Real dues payment data +const payments = ref([]); // Computed const filteredPayments = computed(() => { @@ -521,4 +476,79 @@ const savePayment = () => { console.log('Save payment:', paymentForm.value); showRecordPaymentDialog.value = false; }; + +// Load dues payment data from members +const loadPayments = async () => { + try { + // Fetch members from API + const { data } = await $fetch('/api/members'); + + if (data?.members) { + const paymentRecords = []; + let transactionCounter = 1; + + // Generate payment records from member dues data + for (const member of data.members) { + // If member has last_dues_paid, create a payment record + if (member.last_dues_paid) { + paymentRecords.push({ + id: transactionCounter++, + transaction_id: `TXN-${new Date(member.last_dues_paid).getFullYear()}-${String(transactionCounter).padStart(3, '0')}`, + member_name: `${member.first_name} ${member.last_name}`, + member_email: member.email, + amount: member.dues_amount || 50, // Default annual dues + type: 'Membership Dues', + status: 'Completed', + date: new Date(member.last_dues_paid), + method: member.last_payment_method || 'Unknown' + }); + } + + // If member has dues due/overdue, create a pending payment record + if (member.dues_status === 'Due' || member.dues_status === 'Overdue') { + const dueDate = member.dues_paid_until ? new Date(member.dues_paid_until) : null; + if (dueDate) { + paymentRecords.push({ + id: transactionCounter++, + transaction_id: `TXN-PENDING-${String(transactionCounter).padStart(3, '0')}`, + member_name: `${member.first_name} ${member.last_name}`, + member_email: member.email, + amount: member.dues_amount || 50, + type: 'Membership Dues', + status: member.dues_status === 'Overdue' ? 'Overdue' : 'Pending', + date: dueDate, + method: 'Awaiting Payment' + }); + } + } + } + + // Sort by date descending (most recent first) + paymentRecords.sort((a, b) => b.date.getTime() - a.date.getTime()); + + payments.value = paymentRecords; + + // Calculate stats + const completed = paymentRecords.filter(p => p.status === 'Completed'); + const pending = paymentRecords.filter(p => p.status === 'Pending' || p.status === 'Overdue'); + + stats.value = { + totalRevenue: completed.reduce((sum, p) => sum + p.amount, 0), + pendingPayments: pending.reduce((sum, p) => sum + p.amount, 0), + successfulTransactions: completed.length, + failedTransactions: paymentRecords.filter(p => p.status === 'Failed').length + }; + + console.log(`[admin-payments] Generated ${paymentRecords.length} payment records from member dues data`); + } + } catch (error) { + console.error('Error loading payments:', error); + // Keep empty array if load fails + } +}; + +// Load data on mount +onMounted(async () => { + await loadPayments(); +}); \ No newline at end of file diff --git a/pages/admin/settings/index.vue b/pages/admin/settings/index.vue index b27a748..496c711 100644 --- a/pages/admin/settings/index.vue +++ b/pages/admin/settings/index.vue @@ -15,28 +15,61 @@ mdi-cog General - - mdi-shield-lock - Security - mdi-email Email - - mdi-credit-card - Payments - - - mdi-api - Integrations - + + + + + + + + + + mdi-pencil + Edit Settings + + + + mdi-check + Save + + + mdi-close + Cancel + + + + +

Organization Information

@@ -46,6 +79,10 @@ v-model="settings.general.orgName" label="Organization Name" variant="outlined" + :readonly="!generalEditMode" + :disabled="!generalEditMode" + autocomplete="off" + :class="{ 'readonly-field': !generalEditMode }" />
@@ -54,6 +91,10 @@ label="Contact Email" variant="outlined" type="email" + :readonly="!generalEditMode" + :disabled="!generalEditMode" + autocomplete="off" + :class="{ 'readonly-field': !generalEditMode }" /> @@ -62,6 +103,10 @@ label="Description" variant="outlined" rows="3" + :readonly="!generalEditMode" + :disabled="!generalEditMode" + autocomplete="off" + :class="{ 'readonly-field': !generalEditMode }" /> @@ -76,6 +121,9 @@ label="Timezone" :items="timezones" variant="outlined" + :readonly="!generalEditMode" + :disabled="!generalEditMode" + :class="{ 'readonly-field': !generalEditMode }" /> @@ -84,6 +132,9 @@ label="Date Format" :items="dateFormats" variant="outlined" + :readonly="!generalEditMode" + :disabled="!generalEditMode" + :class="{ 'readonly-field': !generalEditMode }" /> @@ -92,83 +143,9 @@ label="Currency" :items="currencies" variant="outlined" - /> - -
-
-
- - - - - - -

Authentication

-
- - - - - - - - - - - - - - - - -

Password Policy

-
- - - - - - - - - - -
@@ -178,6 +155,70 @@ + + + + + + + + + + + + + mdi-pencil + Edit Email Configuration + + + + mdi-check + Save + + + mdi-email-check + Test + + + mdi-close + Cancel + + + + +

SMTP Configuration

@@ -187,6 +228,11 @@ v-model="settings.email.smtpHost" label="SMTP Host" variant="outlined" + :readonly="!emailEditMode" + :disabled="!emailEditMode" + autocomplete="new-password" + :type="emailEditMode ? 'text' : 'password'" + :class="{ 'readonly-field': !emailEditMode }" />
@@ -195,6 +241,10 @@ label="SMTP Port" variant="outlined" type="number" + :readonly="!emailEditMode" + :disabled="!emailEditMode" + autocomplete="off" + :class="{ 'readonly-field': !emailEditMode }" /> @@ -202,6 +252,11 @@ v-model="settings.email.smtpUsername" label="SMTP Username" variant="outlined" + :readonly="!emailEditMode" + :disabled="!emailEditMode" + autocomplete="new-password" + :type="emailEditMode ? 'text' : 'password'" + :class="{ 'readonly-field': !emailEditMode }" /> @@ -209,14 +264,29 @@ v-model="settings.email.smtpPassword" label="SMTP Password" variant="outlined" - type="password" - /> + :type="showPassword ? 'text' : 'password'" + :readonly="!emailEditMode" + :disabled="!emailEditMode" + autocomplete="new-password" + :class="{ 'readonly-field': !emailEditMode }" + > + + @@ -230,6 +300,10 @@ v-model="settings.email.fromName" label="From Name" variant="outlined" + :readonly="!emailEditMode" + :disabled="!emailEditMode" + autocomplete="off" + :class="{ 'readonly-field': !emailEditMode }" /> @@ -238,6 +312,10 @@ label="From Email" variant="outlined" type="email" + :readonly="!emailEditMode" + :disabled="!emailEditMode" + autocomplete="off" + :class="{ 'readonly-field': !emailEditMode }" /> @@ -249,146 +327,25 @@
- - - - - - -

Payment Gateway

-
- - - - - - - - - - - - - - - - - -

Membership Fees

-
- - - - - - - - - - - - -
-
-
- - - - - - -

Third-Party Integrations

-
- - - - - - - - - - -
{{ integration.name }}
-
- {{ integration.description }} -
-
- - - - - - Configure - - -
-
-
-
-
-
-
-
-
- - - - - - - - Reset to Defaults - - - Save Changes - - + + + + {{ snackbarText }} + + @@ -400,6 +357,16 @@ definePageMeta({ // State const activeTab = ref('general'); +const generalEditMode = ref(false); +const emailEditMode = ref(false); +const showPassword = ref(false); +const testingEmail = ref(false); +const snackbar = ref(false); +const snackbarText = ref(''); +const snackbarColor = ref('success'); + +// Original settings backup for cancel functionality +const originalSettings = ref(null); // Settings data const settings = ref({ @@ -409,17 +376,7 @@ const settings = ref({ orgDescription: 'Monaco USA Association - Connecting Monaco and USA', timezone: 'America/New_York', dateFormat: 'MM/DD/YYYY', - currency: 'USD' - }, - security: { - twoFactor: false, - sso: true, - sessionTimeout: 30, - maxLoginAttempts: 5, - minPasswordLength: 8, - passwordExpiry: 90, - requireSpecialChar: true, - requireNumbers: true + currency: 'EUR' }, email: { smtpHost: 'smtp.gmail.com', @@ -429,15 +386,6 @@ const settings = ref({ useTLS: true, fromName: 'MonacoUSA', fromEmail: 'noreply@monacousa.org' - }, - payments: { - gateway: 'stripe', - publicKey: '', - secretKey: '', - membershipFee: 500, - boardFee: 1000, - lateFee: 50, - autoRenew: true } }); @@ -457,57 +405,137 @@ const dateFormats = [ ]; const currencies = [ - 'USD', 'EUR', + 'USD', 'GBP' ]; -const integrations = ref([ - { - id: 1, - name: 'Google Calendar', - description: 'Sync events with Google Calendar', - icon: 'mdi-google', - enabled: true - }, - { - id: 2, - name: 'Mailchimp', - description: 'Email marketing and newsletters', - icon: 'mdi-email-newsletter', - enabled: false - }, - { - id: 3, - name: 'Slack', - description: 'Team communication and notifications', - icon: 'mdi-slack', - enabled: false - }, - { - id: 4, - name: 'QuickBooks', - description: 'Accounting and financial management', - icon: 'mdi-calculator', - enabled: true - }, - { - id: 5, - name: 'Zoom', - description: 'Virtual meetings and webinars', - icon: 'mdi-video', - enabled: true - } -]); +// Load settings on mount +onMounted(async () => { + await loadSettings(); +}); // Methods -const saveSettings = () => { - console.log('Saving settings:', settings.value); - // Save to API +const loadSettings = async () => { + try { + // Load settings from API + // For now, we'll keep the defaults + console.log('Loading settings...'); + } catch (error) { + console.error('Error loading settings:', error); + showNotification('Failed to load settings', 'error'); + } }; -const resetSettings = () => { - console.log('Resetting to defaults'); - // Reset to default values +const saveGeneralSettings = async () => { + try { + console.log('Saving general settings:', settings.value.general); + // TODO: Save to API + generalEditMode.value = false; + showNotification('General settings saved successfully', 'success'); + } catch (error) { + console.error('Error saving general settings:', error); + showNotification('Failed to save general settings', 'error'); + } }; - \ No newline at end of file + +const cancelGeneralEdit = () => { + if (originalSettings.value) { + settings.value.general = { ...originalSettings.value.general }; + } + generalEditMode.value = false; +}; + +const saveEmailSettings = async () => { + try { + console.log('Saving email settings:', settings.value.email); + // TODO: Save to API + emailEditMode.value = false; + showPassword.value = false; + showNotification('Email settings saved successfully', 'success'); + } catch (error) { + console.error('Error saving email settings:', error); + showNotification('Failed to save email settings', 'error'); + } +}; + +const testEmailSettings = async () => { + testingEmail.value = true; + try { + console.log('Testing email settings...'); + // TODO: Test email configuration via API + await new Promise(resolve => setTimeout(resolve, 2000)); // Simulate API call + showNotification('Test email sent successfully', 'success'); + } catch (error) { + console.error('Error testing email:', error); + showNotification('Failed to send test email', 'error'); + } finally { + testingEmail.value = false; + } +}; + +const cancelEmailEdit = () => { + if (originalSettings.value) { + settings.value.email = { ...originalSettings.value.email }; + } + emailEditMode.value = false; + showPassword.value = false; +}; + +const showNotification = (text: string, color: string = 'success') => { + snackbarText.value = text; + snackbarColor.value = color; + snackbar.value = true; +}; + +// Watch for edit mode changes to backup original settings +watch(generalEditMode, (newVal) => { + if (newVal) { + originalSettings.value = { + general: { ...settings.value.general } + }; + } +}); + +watch(emailEditMode, (newVal) => { + if (newVal) { + originalSettings.value = { + email: { ...settings.value.email } + }; + } +}); + +// Prevent browser autofill on mount +onMounted(() => { + // Disable autofill for all inputs initially + const inputs = document.querySelectorAll('input'); + inputs.forEach(input => { + input.setAttribute('autocomplete', 'off'); + input.setAttribute('data-lpignore', 'true'); // LastPass + input.setAttribute('data-form-type', 'other'); // Dashlane + }); +}); + + + \ No newline at end of file diff --git a/pages/admin/users/index.vue b/pages/admin/users/index.vue index 6ada595..6416ddb 100644 --- a/pages/admin/users/index.vue +++ b/pages/admin/users/index.vue @@ -297,45 +297,8 @@ const headers = [ { title: 'Actions', key: 'actions', sortable: false, align: 'end' } ]; -// Mock data -const users = ref([ - { - id: 1, - name: 'John Smith', - email: 'john.smith@example.com', - role: 'admin', - status: 'active', - lastLogin: new Date('2024-01-15'), - avatar: null - }, - { - id: 2, - name: 'Sarah Johnson', - email: 'sarah.j@example.com', - role: 'board', - status: 'active', - lastLogin: new Date('2024-01-14'), - avatar: null - }, - { - id: 3, - name: 'Mike Wilson', - email: 'mike.w@example.com', - role: 'member', - status: 'active', - lastLogin: new Date('2024-01-13'), - avatar: null - }, - { - id: 4, - name: 'Emma Davis', - email: 'emma.d@example.com', - role: 'member', - status: 'inactive', - lastLogin: new Date('2023-12-01'), - avatar: null - } -]); +// Real data from Keycloak +const users = ref([]); // Computed const filteredUsers = computed(() => { @@ -416,12 +379,37 @@ const saveUser = () => { editingUser.value = null; }; +// Load real users from Keycloak +const loadUsers = async () => { + loading.value = true; + try { + // Fetch users from Keycloak API + const response = await $fetch('/api/admin/users'); + + if (response?.success && response.data?.users) { + // Transform Keycloak users to our format + users.value = response.data.users.map((user: any) => ({ + id: user.id, + name: `${user.firstName || ''} ${user.lastName || ''}`.trim() || user.username, + email: user.email, + role: user.groups?.[0]?.name || 'member', // Use primary group as role + status: user.enabled ? 'active' : 'inactive', + lastLogin: user.lastLogin ? new Date(user.lastLogin) : null, + avatar: null + })); + + console.log(`[admin-users] Loaded ${users.value.length} users from Keycloak`); + } + } catch (error) { + console.error('Error loading users:', error); + // Keep empty array if load fails + } finally { + loading.value = false; + } +}; + // Load data on mount onMounted(async () => { - loading.value = true; - // Fetch users from API - setTimeout(() => { - loading.value = false; - }, 1000); + await loadUsers(); }); \ No newline at end of file diff --git a/pages/board/members/index.vue b/pages/board/members/index.vue index 12f02d4..87dec0b 100644 --- a/pages/board/members/index.vue +++ b/pages/board/members/index.vue @@ -408,74 +408,8 @@ const headers = [ { title: 'Actions', key: 'actions', sortable: false, align: 'center' } ]; -// Mock members data -const members = ref([ - { - id: 1, - memberId: 'MUSA-0001', - firstName: 'John', - lastName: 'Doe', - email: 'john.doe@example.com', - phone: '+1 234 567 8900', - status: 'Active', - duesStatus: 'Paid', - memberType: 'Premium', - joinDate: '2021-03-15', - nationality: 'United States' - }, - { - id: 2, - memberId: 'MUSA-0002', - firstName: 'Jane', - lastName: 'Smith', - email: 'jane.smith@example.com', - phone: '+1 234 567 8901', - status: 'Active', - duesStatus: 'Pending', - memberType: 'Regular', - joinDate: '2022-06-20', - nationality: 'United Kingdom' - }, - { - id: 3, - memberId: 'MUSA-0003', - firstName: 'Pierre', - lastName: 'Dupont', - email: 'pierre.dupont@example.com', - phone: '+33 6 12 34 56 78', - status: 'Active', - duesStatus: 'Paid', - memberType: 'Board', - joinDate: '2020-01-10', - nationality: 'France' - }, - { - id: 4, - memberId: 'MUSA-0004', - firstName: 'Maria', - lastName: 'Rossi', - email: 'maria.rossi@example.com', - phone: '+39 06 123 4567', - status: 'Inactive', - duesStatus: 'Overdue', - memberType: 'Regular', - joinDate: '2021-09-05', - nationality: 'Italy' - }, - { - id: 5, - memberId: 'MUSA-0005', - firstName: 'Hans', - lastName: 'Mueller', - email: 'hans.mueller@example.com', - phone: '+49 30 12345678', - status: 'Active', - duesStatus: 'Paid', - memberType: 'Premium', - joinDate: '2022-02-28', - nationality: 'Germany' - } -]); +// Real members data from API +const members = ref([]); // New member form const newMember = ref({ @@ -589,6 +523,44 @@ const addMember = () => { joinDate: new Date().toISOString().split('T')[0] }; }; + +// Load real members data from API +const loadMembers = async () => { + loading.value = true; + try { + // Fetch members from API + const { data } = await $fetch('/api/members'); + + if (data?.members) { + // Transform the data to match our interface + members.value = data.members.map((member: any) => ({ + id: member.Id || member.id, + memberId: member.member_id || `MUSA-${String(member.Id).padStart(4, '0')}`, + firstName: member.first_name, + lastName: member.last_name, + email: member.email, + phone: member.phone_number || member.phone || '', + status: member.membership_status === 'Active' ? 'Active' : 'Inactive', + duesStatus: member.dues_status || 'Unknown', + memberType: member.membership_type || 'Regular', + joinDate: member.member_since || member.created_at, + nationality: member.nationality || member.country || '' + })); + + console.log(`[board-members] Loaded ${members.value.length} members from API`); + } + } catch (error) { + console.error('Error loading members:', error); + // Keep empty array if load fails + } finally { + loading.value = false; + } +}; + +// Load data on mount +onMounted(async () => { + await loadMembers(); +});