fixes and cleanup
All checks were successful
Build And Push Image / docker (push) Successful in 3m1s
All checks were successful
Build And Push Image / docker (push) Successful in 3m1s
This commit is contained in:
@@ -322,43 +322,47 @@ const handleSubmit = async () => {
|
||||
console.log('[AddMemberDialog] Form keys:', Object.keys(form.value));
|
||||
console.log('[AddMemberDialog] duesPaid switch value:', duesPaid.value);
|
||||
|
||||
// Transform field names to match server expectations (snake_case)
|
||||
// Get current form values
|
||||
const currentForm = unref(form);
|
||||
|
||||
console.log('[AddMemberDialog] Unref form access test:');
|
||||
console.log(' - First Name:', currentForm['First Name']);
|
||||
console.log(' - Last Name:', currentForm['Last Name']);
|
||||
console.log(' - Email:', currentForm.Email);
|
||||
console.log(' - Phone:', currentForm.Phone);
|
||||
|
||||
// Simple approach - send the form data as-is with display names
|
||||
// Let the server handle field normalization
|
||||
const memberData = {
|
||||
first_name: form.value['First Name']?.trim(),
|
||||
last_name: form.value['Last Name']?.trim(),
|
||||
email: form.value.Email?.trim(),
|
||||
phone: form.value.Phone?.trim() || null,
|
||||
date_of_birth: form.value['Date of Birth'] || null,
|
||||
nationality: form.value.Nationality?.trim() || null,
|
||||
address: form.value.Address?.trim() || null,
|
||||
membership_status: form.value['Membership Status'],
|
||||
member_since: form.value['Member Since'] || null,
|
||||
current_year_dues_paid: form.value['Current Year Dues Paid'],
|
||||
membership_date_paid: form.value['Membership Date Paid'] || null,
|
||||
payment_due_date: form.value['Payment Due Date'] || null
|
||||
'First Name': currentForm['First Name']?.trim(),
|
||||
'Last Name': currentForm['Last Name']?.trim(),
|
||||
'Email': currentForm.Email?.trim(),
|
||||
'Phone': currentForm.Phone?.trim() || '',
|
||||
'Date of Birth': currentForm['Date of Birth'] || '',
|
||||
'Nationality': currentForm.Nationality?.trim() || '',
|
||||
'Address': currentForm.Address?.trim() || '',
|
||||
'Membership Status': currentForm['Membership Status'],
|
||||
'Member Since': currentForm['Member Since'] || '',
|
||||
'Current Year Dues Paid': currentForm['Current Year Dues Paid'],
|
||||
'Membership Date Paid': currentForm['Membership Date Paid'] || '',
|
||||
'Payment Due Date': currentForm['Payment Due Date'] || ''
|
||||
};
|
||||
|
||||
console.log('[AddMemberDialog] Field access test:');
|
||||
console.log(' - First Name:', form.value['First Name']);
|
||||
console.log(' - Last Name:', form.value['Last Name']);
|
||||
console.log(' - Email:', form.value.Email);
|
||||
console.log(' - Phone:', form.value.Phone);
|
||||
|
||||
// Ensure required fields are not empty
|
||||
if (!memberData.first_name) {
|
||||
console.error('[AddMemberDialog] First Name is empty. Raw value:', form.value['First Name']);
|
||||
if (!memberData['First Name']) {
|
||||
console.error('[AddMemberDialog] First Name is empty. Raw value:', currentForm['First Name']);
|
||||
throw new Error('First Name is required');
|
||||
}
|
||||
if (!memberData.last_name) {
|
||||
console.error('[AddMemberDialog] Last Name is empty. Raw value:', form.value['Last Name']);
|
||||
if (!memberData['Last Name']) {
|
||||
console.error('[AddMemberDialog] Last Name is empty. Raw value:', currentForm['Last Name']);
|
||||
throw new Error('Last Name is required');
|
||||
}
|
||||
if (!memberData.email) {
|
||||
console.error('[AddMemberDialog] Email is empty. Raw value:', form.value.Email);
|
||||
if (!memberData['Email']) {
|
||||
console.error('[AddMemberDialog] Email is empty. Raw value:', currentForm.Email);
|
||||
throw new Error('Email is required');
|
||||
}
|
||||
|
||||
console.log('[AddMemberDialog] Transformed memberData:', JSON.stringify(memberData, null, 2));
|
||||
console.log('[AddMemberDialog] Final memberData:', JSON.stringify(memberData, null, 2));
|
||||
console.log('[AddMemberDialog] About to submit to API...');
|
||||
|
||||
const response = await $fetch<{ success: boolean; data: Member; message?: string }>('/api/members', {
|
||||
|
||||
@@ -1,147 +0,0 @@
|
||||
<template>
|
||||
<div class="enhanced-phone-input">
|
||||
<PhoneInput
|
||||
v-model="phoneValue"
|
||||
:country-code="selectedCountryCode"
|
||||
:placeholder="placeholder || 'Enter phone number'"
|
||||
:preferred-countries="['US', 'FR', 'GB', 'DE', 'CA', 'AU', 'ES', 'IT', 'NL', 'BE']"
|
||||
:auto-format="true"
|
||||
:no-formatting-as-you-type="false"
|
||||
country-locale="en-US"
|
||||
@update="handlePhoneUpdate"
|
||||
@input="handlePhoneInput"
|
||||
class="phone-input-wrapper"
|
||||
>
|
||||
<template #input="{ inputValue, updateInputValue, placeholder: inputPlaceholder }">
|
||||
<v-text-field
|
||||
:model-value="inputValue"
|
||||
@update:model-value="updateInputValue"
|
||||
:placeholder="inputPlaceholder"
|
||||
:label="label"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
:error="hasError"
|
||||
:error-messages="errorMessage"
|
||||
:rules="rules"
|
||||
class="phone-number-input"
|
||||
hide-details="auto"
|
||||
/>
|
||||
</template>
|
||||
</PhoneInput>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import PhoneInput from 'base-vue-phone-input';
|
||||
import type { Results as PhoneResults } from 'base-vue-phone-input';
|
||||
|
||||
interface Props {
|
||||
modelValue?: string;
|
||||
label?: string;
|
||||
placeholder?: string;
|
||||
error?: boolean;
|
||||
errorMessage?: string;
|
||||
rules?: Array<(value: any) => boolean | string>;
|
||||
}
|
||||
|
||||
interface Emits {
|
||||
(e: 'update:model-value', value: string): void;
|
||||
(e: 'phone-data', data: PhoneResults): void;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
modelValue: '',
|
||||
label: 'Phone Number',
|
||||
placeholder: 'Enter phone number',
|
||||
error: false,
|
||||
errorMessage: '',
|
||||
rules: () => []
|
||||
});
|
||||
|
||||
const emit = defineEmits<Emits>();
|
||||
|
||||
// Local state
|
||||
const phoneValue = ref(props.modelValue);
|
||||
const selectedCountryCode = ref('US');
|
||||
const phoneData = ref<PhoneResults | null>(null);
|
||||
|
||||
// Computed properties
|
||||
const hasError = computed(() => props.error);
|
||||
|
||||
// Watch for external changes to modelValue
|
||||
watch(() => props.modelValue, (newValue) => {
|
||||
if (newValue !== phoneValue.value) {
|
||||
phoneValue.value = newValue;
|
||||
}
|
||||
});
|
||||
|
||||
// Handle phone input events
|
||||
const handlePhoneInput = (value: string) => {
|
||||
phoneValue.value = value;
|
||||
emit('update:model-value', value);
|
||||
};
|
||||
|
||||
const handlePhoneUpdate = (data: PhoneResults) => {
|
||||
phoneData.value = data;
|
||||
|
||||
// Update country code if available
|
||||
if (data.countryCode) {
|
||||
selectedCountryCode.value = data.countryCode;
|
||||
}
|
||||
|
||||
// Emit the formatted phone number
|
||||
const formattedNumber = data.formatInternational || data.e164 || phoneValue.value;
|
||||
if (formattedNumber !== phoneValue.value) {
|
||||
phoneValue.value = formattedNumber;
|
||||
emit('update:model-value', formattedNumber);
|
||||
}
|
||||
|
||||
// Emit phone data for parent component
|
||||
emit('phone-data', data);
|
||||
};
|
||||
|
||||
// Initialize with modelValue
|
||||
onMounted(() => {
|
||||
if (props.modelValue) {
|
||||
phoneValue.value = props.modelValue;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.enhanced-phone-input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.phone-input-wrapper {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.country-select {
|
||||
flex: 0 0 120px;
|
||||
}
|
||||
|
||||
.phone-number-input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.flag-emoji {
|
||||
font-size: 1.2em;
|
||||
}
|
||||
|
||||
.country-code {
|
||||
font-size: 0.875rem;
|
||||
color: rgba(var(--v-theme-on-surface), 0.87);
|
||||
}
|
||||
|
||||
/* Ensure proper spacing in Vuetify forms */
|
||||
:deep(.v-input) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
:deep(.v-input__details) {
|
||||
min-height: 20px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,7 +1,5 @@
|
||||
<template>
|
||||
<div class="multiple-nationality-input">
|
||||
<v-label v-if="label" class="v-label mb-2">{{ label }}</v-label>
|
||||
|
||||
<div class="nationality-list">
|
||||
<div
|
||||
v-for="(nationality, index) in nationalities"
|
||||
@@ -11,11 +9,11 @@
|
||||
<v-select
|
||||
v-model="nationalities[index]"
|
||||
:items="countryOptions"
|
||||
:label="`Nationality ${index + 1}`"
|
||||
:label="index === 0 && label ? label : `Nationality ${index + 1}`"
|
||||
variant="outlined"
|
||||
density="comfortable"
|
||||
:error="hasError"
|
||||
:error-messages="errorMessage"
|
||||
:error="hasError && index === 0"
|
||||
:error-messages="hasError && index === 0 ? errorMessage : undefined"
|
||||
@update:model-value="updateNationalities"
|
||||
class="nationality-select"
|
||||
>
|
||||
|
||||
@@ -1,164 +0,0 @@
|
||||
<template>
|
||||
<div class="demo-container">
|
||||
<v-card class="pa-6" elevation="2">
|
||||
<v-card-title class="text-h5 mb-4">
|
||||
📱 Professional Phone Input - Desktop & Mobile Optimized
|
||||
</v-card-title>
|
||||
|
||||
<v-row>
|
||||
<v-col cols="12" md="6">
|
||||
<PhoneInputWrapper
|
||||
v-model="phoneNumber"
|
||||
label="Phone Number"
|
||||
placeholder="Enter your phone number"
|
||||
help-text="Clean Vuetify design with advanced mobile optimization"
|
||||
@phone-data="handlePhoneData"
|
||||
@country-changed="handleCountryChange"
|
||||
/>
|
||||
|
||||
<div class="mt-4">
|
||||
<v-btn
|
||||
color="primary"
|
||||
@click="testUSNumber"
|
||||
variant="outlined"
|
||||
size="small"
|
||||
class="mr-2 mb-2"
|
||||
>
|
||||
Test: (917) 932-4061
|
||||
</v-btn>
|
||||
|
||||
<v-btn
|
||||
color="secondary"
|
||||
@click="testMonacoNumber"
|
||||
variant="outlined"
|
||||
size="small"
|
||||
class="mr-2 mb-2"
|
||||
>
|
||||
Test: Monaco +377
|
||||
</v-btn>
|
||||
|
||||
<v-btn
|
||||
color="accent"
|
||||
@click="testFrenchNumber"
|
||||
variant="outlined"
|
||||
size="small"
|
||||
class="mr-2 mb-2"
|
||||
>
|
||||
Test: France +33
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-col>
|
||||
|
||||
<v-col cols="12" md="6">
|
||||
<v-card variant="outlined" class="pa-4">
|
||||
<v-card-title class="text-subtitle-1">Live Phone Data:</v-card-title>
|
||||
<div class="mt-2">
|
||||
<p><strong>Number:</strong> <code>{{ phoneNumber }}</code></p>
|
||||
<p><strong>Valid:</strong> {{ phoneData?.isValid ? '✅ Valid' : '❌ Invalid' }}</p>
|
||||
<p><strong>Country:</strong> {{ phoneData?.country?.name }} ({{ phoneData?.country?.iso2 }})</p>
|
||||
<p><strong>Dial Code:</strong> {{ phoneData?.country?.dialCode }}</p>
|
||||
<p><strong>Device:</strong> {{ isMobile ? '📱 Mobile' : '🖥️ Desktop' }}</p>
|
||||
</div>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
|
||||
<v-alert type="success" variant="tonal" class="mt-6">
|
||||
<template #title>🎯 Perfect Desktop Implementation:</template>
|
||||
<ul class="mt-2">
|
||||
<li><strong>Clean Design:</strong> Exactly like your screenshot - Vuetify text field with flag inside</li>
|
||||
<li><strong>Compact Dropdown:</strong> Max 240px height, not oversized</li>
|
||||
<li><strong>Real Flags:</strong> High-quality country flags from flagcdn.com</li>
|
||||
<li><strong>Monaco Priority:</strong> 🇲🇨 Monaco and 🇫🇷 France appear first</li>
|
||||
<li><strong>Smart Formatting:</strong> Uses libphonenumber-js for proper formatting</li>
|
||||
<li><strong>Search Functionality:</strong> Type to find countries quickly</li>
|
||||
</ul>
|
||||
</v-alert>
|
||||
|
||||
<v-alert type="info" variant="tonal" class="mt-4">
|
||||
<template #title>📱 Advanced Mobile Optimization:</template>
|
||||
<ul class="mt-2">
|
||||
<li><strong>Mobile Detection:</strong> Automatic device detection with resize handling</li>
|
||||
<li><strong>Full-Screen Modal:</strong> Mobile dropdown opens as centered modal with overlay</li>
|
||||
<li><strong>Touch-Friendly:</strong> All elements sized for proper touch targets (44px+)</li>
|
||||
<li><strong>iOS Safari Fixes:</strong> Prevents zoom on focus, proper appearance handling</li>
|
||||
<li><strong>Android Material:</strong> Material Design touch targets and interactions</li>
|
||||
<li><strong>Accessibility:</strong> Reduced motion, high contrast, screen reader support</li>
|
||||
<li><strong>Safe Areas:</strong> Handles notched devices with proper padding</li>
|
||||
<li><strong>Landscape Support:</strong> Optimized layout for landscape orientation</li>
|
||||
<li><strong>Backdrop Blur:</strong> Modern glass morphism effects on supported devices</li>
|
||||
<li><strong>Smooth Scrolling:</strong> Native touch scrolling with momentum</li>
|
||||
</ul>
|
||||
</v-alert>
|
||||
|
||||
<v-alert type="warning" variant="tonal" class="mt-4">
|
||||
<template #title>🧪 Test Mobile Features:</template>
|
||||
<p class="mt-2 mb-2">To test mobile features, try:</p>
|
||||
<ul>
|
||||
<li><strong>Resize Window:</strong> Make browser window narrow (< 768px)</li>
|
||||
<li><strong>Developer Tools:</strong> Toggle device emulation in Chrome DevTools</li>
|
||||
<li><strong>Touch Device:</strong> Test on actual phone/tablet for best experience</li>
|
||||
</ul>
|
||||
</v-alert>
|
||||
</v-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import PhoneInputWrapper from './PhoneInputWrapper.vue';
|
||||
|
||||
const phoneNumber = ref('');
|
||||
const phoneData = ref<any>(null);
|
||||
const isMobile = ref(false);
|
||||
|
||||
// Mobile detection
|
||||
onMounted(() => {
|
||||
const checkMobile = () => {
|
||||
isMobile.value = window.innerWidth <= 768 || 'ontouchstart' in window;
|
||||
};
|
||||
|
||||
checkMobile();
|
||||
window.addEventListener('resize', checkMobile);
|
||||
|
||||
onUnmounted(() => {
|
||||
window.removeEventListener('resize', checkMobile);
|
||||
});
|
||||
});
|
||||
|
||||
const handlePhoneData = (data: any) => {
|
||||
phoneData.value = data;
|
||||
console.log('Phone data:', data);
|
||||
};
|
||||
|
||||
const handleCountryChange = (country: any) => {
|
||||
console.log('Country changed:', country);
|
||||
};
|
||||
|
||||
const testUSNumber = () => {
|
||||
phoneNumber.value = '+19179324061';
|
||||
};
|
||||
|
||||
const testMonacoNumber = () => {
|
||||
phoneNumber.value = '+37799123456';
|
||||
};
|
||||
|
||||
const testFrenchNumber = () => {
|
||||
phoneNumber.value = '+33123456789';
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.demo-container {
|
||||
max-width: 1000px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
code {
|
||||
background: rgba(var(--v-theme-primary), 0.1);
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-family: 'Roboto Mono', monospace;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
</style>
|
||||
@@ -35,7 +35,6 @@
|
||||
'country-selector--open': dropdownOpen,
|
||||
'country-selector--mobile': isMobile
|
||||
}"
|
||||
@click.stop="toggleDropdown"
|
||||
>
|
||||
<img
|
||||
:src="flagUrl"
|
||||
|
||||
Reference in New Issue
Block a user