feat(beneficiaries): Display customName in beneficiaries list
- Add displayName (customName || name) to BeneficiaryCard component - Update header and MockDashboard to show customName when set - Add custom name editing for non-custodian users (guardian/caretaker) - Backend PATCH endpoint now supports customName updates via user_access table 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
4bdfa69dbe
commit
c058ebe2c6
@ -73,7 +73,12 @@ export default function BeneficiaryDetailScreen() {
|
|||||||
|
|
||||||
// Edit modal state
|
// Edit modal state
|
||||||
const [isEditModalVisible, setIsEditModalVisible] = useState(false);
|
const [isEditModalVisible, setIsEditModalVisible] = useState(false);
|
||||||
const [editForm, setEditForm] = useState({ name: '', address: '', avatar: undefined as string | undefined });
|
const [editForm, setEditForm] = useState({
|
||||||
|
name: '',
|
||||||
|
address: '',
|
||||||
|
avatar: undefined as string | undefined,
|
||||||
|
customName: '' // For non-custodian users
|
||||||
|
});
|
||||||
const [isSavingEdit, setIsSavingEdit] = useState(false);
|
const [isSavingEdit, setIsSavingEdit] = useState(false);
|
||||||
const [isUploadingAvatar, setIsUploadingAvatar] = useState(false);
|
const [isUploadingAvatar, setIsUploadingAvatar] = useState(false);
|
||||||
|
|
||||||
@ -215,11 +220,15 @@ export default function BeneficiaryDetailScreen() {
|
|||||||
name: beneficiary.name || '',
|
name: beneficiary.name || '',
|
||||||
address: beneficiary.address || '',
|
address: beneficiary.address || '',
|
||||||
avatar: beneficiary.avatar,
|
avatar: beneficiary.avatar,
|
||||||
|
customName: beneficiary.customName || '',
|
||||||
});
|
});
|
||||||
setIsEditModalVisible(true);
|
setIsEditModalVisible(true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Check if user is custodian (can edit all fields)
|
||||||
|
const isCustodian = beneficiary?.role === 'custodian';
|
||||||
|
|
||||||
const handlePickAvatar = async () => {
|
const handlePickAvatar = async () => {
|
||||||
const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync();
|
const { status } = await ImagePicker.requestMediaLibraryPermissionsAsync();
|
||||||
if (status !== 'granted') {
|
if (status !== 'granted') {
|
||||||
@ -240,8 +249,8 @@ export default function BeneficiaryDetailScreen() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleSaveEdit = async () => {
|
const handleSaveEdit = async () => {
|
||||||
if (!editForm.name.trim() || !id) {
|
if (!id) {
|
||||||
toast.error('Error', 'Name is required');
|
toast.error('Error', 'Invalid beneficiary');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -249,32 +258,51 @@ export default function BeneficiaryDetailScreen() {
|
|||||||
setIsSavingEdit(true);
|
setIsSavingEdit(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Update basic info
|
if (isCustodian) {
|
||||||
const response = await api.updateWellNuoBeneficiary(beneficiaryId, {
|
// Custodian: update name, address in beneficiaries table
|
||||||
name: editForm.name.trim(),
|
if (!editForm.name.trim()) {
|
||||||
address: editForm.address.trim() || undefined,
|
toast.error('Error', 'Name is required');
|
||||||
});
|
setIsSavingEdit(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!response.ok) {
|
const response = await api.updateWellNuoBeneficiary(beneficiaryId, {
|
||||||
toast.error('Error', response.error?.message || 'Failed to save changes.');
|
name: editForm.name.trim(),
|
||||||
setIsSavingEdit(false);
|
address: editForm.address.trim() || undefined,
|
||||||
return;
|
});
|
||||||
}
|
|
||||||
|
|
||||||
// Upload avatar if changed (new local file URI)
|
if (!response.ok) {
|
||||||
if (editForm.avatar && editForm.avatar.startsWith('file://')) {
|
toast.error('Error', response.error?.message || 'Failed to save changes.');
|
||||||
setIsUploadingAvatar(true);
|
setIsSavingEdit(false);
|
||||||
const avatarResult = await api.updateBeneficiaryAvatar(beneficiaryId, editForm.avatar);
|
return;
|
||||||
setIsUploadingAvatar(false);
|
}
|
||||||
if (!avatarResult.ok) {
|
|
||||||
console.warn('[BeneficiaryDetail] Failed to upload avatar:', avatarResult.error?.message);
|
// Upload avatar if changed (new local file URI)
|
||||||
// Show info but don't fail the whole operation
|
if (editForm.avatar && editForm.avatar.startsWith('file://')) {
|
||||||
toast.info('Note', 'Profile saved but avatar upload failed');
|
setIsUploadingAvatar(true);
|
||||||
|
const avatarResult = await api.updateBeneficiaryAvatar(beneficiaryId, editForm.avatar);
|
||||||
|
setIsUploadingAvatar(false);
|
||||||
|
if (!avatarResult.ok) {
|
||||||
|
console.warn('[BeneficiaryDetail] Failed to upload avatar:', avatarResult.error?.message);
|
||||||
|
toast.info('Note', 'Profile saved but avatar upload failed');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Guardian/Caretaker: update only customName in user_access table
|
||||||
|
const response = await api.updateBeneficiaryCustomName(
|
||||||
|
beneficiaryId,
|
||||||
|
editForm.customName.trim() || null
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
toast.error('Error', response.error?.message || 'Failed to save nickname.');
|
||||||
|
setIsSavingEdit(false);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setIsEditModalVisible(false);
|
setIsEditModalVisible(false);
|
||||||
toast.success('Saved', 'Profile updated successfully');
|
toast.success('Saved', isCustodian ? 'Profile updated successfully' : 'Nickname saved');
|
||||||
loadBeneficiary(false);
|
loadBeneficiary(false);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
toast.error('Error', 'Failed to save changes.');
|
toast.error('Error', 'Failed to save changes.');
|
||||||
@ -375,7 +403,7 @@ export default function BeneficiaryDetailScreen() {
|
|||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
<Text style={styles.headerTitle}>{beneficiary.name}</Text>
|
<Text style={styles.headerTitle}>{beneficiary.customName || beneficiary.name}</Text>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<BeneficiaryMenu
|
<BeneficiaryMenu
|
||||||
@ -471,7 +499,7 @@ export default function BeneficiaryDetailScreen() {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<MockDashboard beneficiaryName={beneficiary.name} />
|
<MockDashboard beneficiaryName={beneficiary.customName || beneficiary.name} />
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
@ -485,64 +513,94 @@ export default function BeneficiaryDetailScreen() {
|
|||||||
>
|
>
|
||||||
<View style={styles.modalContainer}>
|
<View style={styles.modalContainer}>
|
||||||
<View style={styles.modalHeader}>
|
<View style={styles.modalHeader}>
|
||||||
<Text style={styles.modalTitle}>Edit Profile</Text>
|
<Text style={styles.modalTitle}>
|
||||||
|
{isCustodian ? 'Edit Profile' : 'Edit Nickname'}
|
||||||
|
</Text>
|
||||||
<TouchableOpacity onPress={() => setIsEditModalVisible(false)}>
|
<TouchableOpacity onPress={() => setIsEditModalVisible(false)}>
|
||||||
<Ionicons name="close" size={24} color={AppColors.textPrimary} />
|
<Ionicons name="close" size={24} color={AppColors.textPrimary} />
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
</View>
|
</View>
|
||||||
|
|
||||||
<ScrollView style={styles.modalContent}>
|
<ScrollView style={styles.modalContent}>
|
||||||
{/* Avatar */}
|
{isCustodian ? (
|
||||||
<TouchableOpacity
|
<>
|
||||||
style={styles.avatarPicker}
|
{/* Custodian: Avatar, Name, Address */}
|
||||||
onPress={handlePickAvatar}
|
<TouchableOpacity
|
||||||
disabled={isSavingEdit}
|
style={styles.avatarPicker}
|
||||||
>
|
onPress={handlePickAvatar}
|
||||||
{editForm.avatar ? (
|
disabled={isSavingEdit}
|
||||||
<Image source={{ uri: editForm.avatar }} style={styles.avatarPickerImage} />
|
>
|
||||||
) : (
|
{editForm.avatar ? (
|
||||||
<View style={styles.avatarPickerPlaceholder}>
|
<Image source={{ uri: editForm.avatar }} style={styles.avatarPickerImage} />
|
||||||
<Ionicons name="camera" size={32} color={AppColors.textMuted} />
|
) : (
|
||||||
</View>
|
<View style={styles.avatarPickerPlaceholder}>
|
||||||
)}
|
<Ionicons name="camera" size={32} color={AppColors.textMuted} />
|
||||||
{isUploadingAvatar && (
|
</View>
|
||||||
<View style={styles.avatarUploadOverlay}>
|
)}
|
||||||
<ActivityIndicator size="large" color={AppColors.white} />
|
{isUploadingAvatar && (
|
||||||
<Text style={styles.avatarUploadText}>Uploading...</Text>
|
<View style={styles.avatarUploadOverlay}>
|
||||||
</View>
|
<ActivityIndicator size="large" color={AppColors.white} />
|
||||||
)}
|
<Text style={styles.avatarUploadText}>Uploading...</Text>
|
||||||
{!isUploadingAvatar && (
|
</View>
|
||||||
<View style={styles.avatarPickerBadge}>
|
)}
|
||||||
<Ionicons name="pencil" size={12} color={AppColors.white} />
|
{!isUploadingAvatar && (
|
||||||
</View>
|
<View style={styles.avatarPickerBadge}>
|
||||||
)}
|
<Ionicons name="pencil" size={12} color={AppColors.white} />
|
||||||
</TouchableOpacity>
|
</View>
|
||||||
|
)}
|
||||||
|
</TouchableOpacity>
|
||||||
|
|
||||||
{/* Name */}
|
<View style={styles.inputGroup}>
|
||||||
<View style={styles.inputGroup}>
|
<Text style={styles.inputLabel}>Name</Text>
|
||||||
<Text style={styles.inputLabel}>Name</Text>
|
<TextInput
|
||||||
<TextInput
|
style={styles.textInput}
|
||||||
style={styles.textInput}
|
value={editForm.name}
|
||||||
value={editForm.name}
|
onChangeText={(text) => setEditForm(prev => ({ ...prev, name: text }))}
|
||||||
onChangeText={(text) => setEditForm(prev => ({ ...prev, name: text }))}
|
placeholder="Full name"
|
||||||
placeholder="Full name"
|
placeholderTextColor={AppColors.textMuted}
|
||||||
placeholderTextColor={AppColors.textMuted}
|
/>
|
||||||
/>
|
</View>
|
||||||
</View>
|
|
||||||
|
|
||||||
{/* Address */}
|
<View style={styles.inputGroup}>
|
||||||
<View style={styles.inputGroup}>
|
<Text style={styles.inputLabel}>Address</Text>
|
||||||
<Text style={styles.inputLabel}>Address</Text>
|
<TextInput
|
||||||
<TextInput
|
style={[styles.textInput, styles.textArea]}
|
||||||
style={[styles.textInput, styles.textArea]}
|
value={editForm.address}
|
||||||
value={editForm.address}
|
onChangeText={(text) => setEditForm(prev => ({ ...prev, address: text }))}
|
||||||
onChangeText={(text) => setEditForm(prev => ({ ...prev, address: text }))}
|
placeholder="Street address"
|
||||||
placeholder="Street address"
|
placeholderTextColor={AppColors.textMuted}
|
||||||
placeholderTextColor={AppColors.textMuted}
|
multiline
|
||||||
multiline
|
numberOfLines={3}
|
||||||
numberOfLines={3}
|
/>
|
||||||
/>
|
</View>
|
||||||
</View>
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
{/* Guardian/Caretaker: Only custom nickname */}
|
||||||
|
<View style={styles.nicknameInfo}>
|
||||||
|
<Text style={styles.nicknameInfoText}>
|
||||||
|
Set a personal nickname for {beneficiary?.name}. This is only visible to you.
|
||||||
|
</Text>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.inputGroup}>
|
||||||
|
<Text style={styles.inputLabel}>Nickname</Text>
|
||||||
|
<TextInput
|
||||||
|
style={styles.textInput}
|
||||||
|
value={editForm.customName}
|
||||||
|
onChangeText={(text) => setEditForm(prev => ({ ...prev, customName: text }))}
|
||||||
|
placeholder={`e.g., "Mom", "Dad", "Grandma"`}
|
||||||
|
placeholderTextColor={AppColors.textMuted}
|
||||||
|
maxLength={100}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
|
||||||
|
<View style={styles.originalNameContainer}>
|
||||||
|
<Text style={styles.originalNameLabel}>Original name:</Text>
|
||||||
|
<Text style={styles.originalNameValue}>{beneficiary?.name}</Text>
|
||||||
|
</View>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
|
|
||||||
<View style={styles.modalFooter}>
|
<View style={styles.modalFooter}>
|
||||||
@ -819,4 +877,34 @@ const styles = StyleSheet.create({
|
|||||||
buttonDisabled: {
|
buttonDisabled: {
|
||||||
opacity: 0.6,
|
opacity: 0.6,
|
||||||
},
|
},
|
||||||
|
// Non-custodian edit modal styles
|
||||||
|
nicknameInfo: {
|
||||||
|
backgroundColor: AppColors.surfaceSecondary,
|
||||||
|
padding: Spacing.md,
|
||||||
|
borderRadius: BorderRadius.md,
|
||||||
|
marginBottom: Spacing.lg,
|
||||||
|
},
|
||||||
|
nicknameInfoText: {
|
||||||
|
fontSize: FontSizes.sm,
|
||||||
|
color: AppColors.textSecondary,
|
||||||
|
lineHeight: 20,
|
||||||
|
},
|
||||||
|
originalNameContainer: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginTop: Spacing.sm,
|
||||||
|
paddingTop: Spacing.md,
|
||||||
|
borderTopWidth: 1,
|
||||||
|
borderTopColor: AppColors.border,
|
||||||
|
},
|
||||||
|
originalNameLabel: {
|
||||||
|
fontSize: FontSizes.sm,
|
||||||
|
color: AppColors.textMuted,
|
||||||
|
marginRight: Spacing.xs,
|
||||||
|
},
|
||||||
|
originalNameValue: {
|
||||||
|
fontSize: FontSizes.sm,
|
||||||
|
color: AppColors.textSecondary,
|
||||||
|
fontWeight: FontWeights.medium,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@ -89,6 +89,9 @@ function BeneficiaryCard({ beneficiary, onPress, onActivate }: BeneficiaryCardPr
|
|||||||
|
|
||||||
const statusConfig = equipmentStatus ? equipmentStatusConfig[equipmentStatus as keyof typeof equipmentStatusConfig] : equipmentStatusConfig.none;
|
const statusConfig = equipmentStatus ? equipmentStatusConfig[equipmentStatus as keyof typeof equipmentStatusConfig] : equipmentStatusConfig.none;
|
||||||
|
|
||||||
|
// Display name: customName (e.g., "Mom") if set, otherwise full name
|
||||||
|
const displayName = beneficiary.customName || beneficiary.name;
|
||||||
|
|
||||||
// Check if avatar is valid (not empty, null, or placeholder)
|
// Check if avatar is valid (not empty, null, or placeholder)
|
||||||
const hasValidAvatar = beneficiary.avatar &&
|
const hasValidAvatar = beneficiary.avatar &&
|
||||||
beneficiary.avatar.trim() !== '' &&
|
beneficiary.avatar.trim() !== '' &&
|
||||||
@ -116,7 +119,7 @@ function BeneficiaryCard({ beneficiary, onPress, onActivate }: BeneficiaryCardPr
|
|||||||
) : (
|
) : (
|
||||||
<View style={[styles.avatar, hasNoSubscription && styles.avatarNoSubscription]}>
|
<View style={[styles.avatar, hasNoSubscription && styles.avatarNoSubscription]}>
|
||||||
<Text style={[styles.avatarText, hasNoSubscription && styles.avatarTextNoSubscription]}>
|
<Text style={[styles.avatarText, hasNoSubscription && styles.avatarTextNoSubscription]}>
|
||||||
{beneficiary.name.charAt(0).toUpperCase()}
|
{displayName.charAt(0).toUpperCase()}
|
||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
)}
|
)}
|
||||||
@ -124,7 +127,7 @@ function BeneficiaryCard({ beneficiary, onPress, onActivate }: BeneficiaryCardPr
|
|||||||
|
|
||||||
{/* Name and Status */}
|
{/* Name and Status */}
|
||||||
<View style={styles.info}>
|
<View style={styles.info}>
|
||||||
<Text style={styles.name} numberOfLines={1}>{beneficiary.name}</Text>
|
<Text style={styles.name} numberOfLines={1}>{displayName}</Text>
|
||||||
{/* User's role for this beneficiary */}
|
{/* User's role for this beneficiary */}
|
||||||
{beneficiary.role && (
|
{beneficiary.role && (
|
||||||
<Text style={styles.roleText}>
|
<Text style={styles.roleText}>
|
||||||
@ -260,9 +263,10 @@ export default function HomeScreen() {
|
|||||||
|
|
||||||
const handleActivate = (beneficiary: Beneficiary) => {
|
const handleActivate = (beneficiary: Beneficiary) => {
|
||||||
setCurrentBeneficiary(beneficiary);
|
setCurrentBeneficiary(beneficiary);
|
||||||
|
const lovedOneName = beneficiary.customName || beneficiary.name;
|
||||||
router.push({
|
router.push({
|
||||||
pathname: '/(auth)/activate',
|
pathname: '/(auth)/activate',
|
||||||
params: { lovedOneName: beneficiary.name, beneficiaryId: beneficiary.id.toString() },
|
params: { lovedOneName, beneficiaryId: beneficiary.id.toString() },
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -508,8 +508,9 @@ router.post('/', async (req, res) => {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* PATCH /api/me/beneficiaries/:id
|
* PATCH /api/me/beneficiaries/:id
|
||||||
* Updates beneficiary info (requires custodian or guardian role)
|
* Updates beneficiary info
|
||||||
* Now uses the proper beneficiaries table (not users)
|
* - Custodian: can update name, phone, address in beneficiaries table
|
||||||
|
* - Guardian/Caretaker: can only update customName in user_access table
|
||||||
*/
|
*/
|
||||||
router.patch('/:id', async (req, res) => {
|
router.patch('/:id', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
@ -518,53 +519,110 @@ router.patch('/:id', async (req, res) => {
|
|||||||
|
|
||||||
console.log('[BENEFICIARY PATCH] Request:', { userId, beneficiaryId, body: req.body });
|
console.log('[BENEFICIARY PATCH] Request:', { userId, beneficiaryId, body: req.body });
|
||||||
|
|
||||||
// Check user has custodian or guardian access - using beneficiary_id
|
// Check user has access - using beneficiary_id
|
||||||
const { data: access, error: accessError } = await supabase
|
const { data: access, error: accessError } = await supabase
|
||||||
.from('user_access')
|
.from('user_access')
|
||||||
.select('role')
|
.select('id, role')
|
||||||
.eq('accessor_id', userId)
|
.eq('accessor_id', userId)
|
||||||
.eq('beneficiary_id', beneficiaryId)
|
.eq('beneficiary_id', beneficiaryId)
|
||||||
.single();
|
.single();
|
||||||
|
|
||||||
if (accessError || !access || !['custodian', 'guardian'].includes(access.role)) {
|
if (accessError || !access) {
|
||||||
return res.status(403).json({ error: 'Only custodian or guardian can update beneficiary info' });
|
return res.status(403).json({ error: 'Access denied to this beneficiary' });
|
||||||
}
|
}
|
||||||
|
|
||||||
const { name, phone, address } = req.body;
|
const { name, phone, address, customName } = req.body;
|
||||||
|
const isCustodian = access.role === 'custodian';
|
||||||
|
|
||||||
const updateData = {
|
// Custodian can update beneficiary data (name, phone, address)
|
||||||
updated_at: new Date().toISOString()
|
if (isCustodian) {
|
||||||
};
|
const updateData = {
|
||||||
|
updated_at: new Date().toISOString()
|
||||||
|
};
|
||||||
|
|
||||||
if (name !== undefined) updateData.name = name;
|
if (name !== undefined) updateData.name = name;
|
||||||
if (phone !== undefined) updateData.phone = phone;
|
if (phone !== undefined) updateData.phone = phone;
|
||||||
if (address !== undefined) updateData.address = address;
|
if (address !== undefined) updateData.address = address;
|
||||||
|
|
||||||
// Update in beneficiaries table
|
// Update in beneficiaries table
|
||||||
const { data: beneficiary, error } = await supabase
|
const { data: beneficiary, error } = await supabase
|
||||||
.from('beneficiaries')
|
.from('beneficiaries')
|
||||||
.update(updateData)
|
.update(updateData)
|
||||||
.eq('id', beneficiaryId)
|
.eq('id', beneficiaryId)
|
||||||
.select()
|
.select()
|
||||||
.single();
|
.single();
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
console.error('[BENEFICIARY PATCH] Supabase error:', error);
|
console.error('[BENEFICIARY PATCH] Supabase error:', error);
|
||||||
return res.status(500).json({ error: 'Failed to update beneficiary' });
|
return res.status(500).json({ error: 'Failed to update beneficiary' });
|
||||||
}
|
|
||||||
|
|
||||||
console.log('[BENEFICIARY PATCH] Success:', { id: beneficiary.id, name: beneficiary.name, address: beneficiary.address });
|
|
||||||
|
|
||||||
res.json({
|
|
||||||
success: true,
|
|
||||||
beneficiary: {
|
|
||||||
id: beneficiary.id,
|
|
||||||
name: beneficiary.name,
|
|
||||||
phone: beneficiary.phone,
|
|
||||||
address: beneficiary.address || null,
|
|
||||||
avatarUrl: beneficiary.avatar_url
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
console.log('[BENEFICIARY PATCH] Custodian updated:', { id: beneficiary.id, name: beneficiary.name, address: beneficiary.address });
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
beneficiary: {
|
||||||
|
id: beneficiary.id,
|
||||||
|
name: beneficiary.name,
|
||||||
|
displayName: beneficiary.name, // For custodian, displayName = name
|
||||||
|
originalName: beneficiary.name,
|
||||||
|
phone: beneficiary.phone,
|
||||||
|
address: beneficiary.address || null,
|
||||||
|
avatarUrl: beneficiary.avatar_url
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Guardian/Caretaker can only update their custom_name
|
||||||
|
if (customName === undefined) {
|
||||||
|
return res.status(400).json({ error: 'customName is required for non-custodian roles' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate custom name
|
||||||
|
if (customName !== null && typeof customName !== 'string') {
|
||||||
|
return res.status(400).json({ error: 'customName must be a string or null' });
|
||||||
|
}
|
||||||
|
if (customName && customName.length > 100) {
|
||||||
|
return res.status(400).json({ error: 'customName must be 100 characters or less' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update custom_name in user_access
|
||||||
|
const { error: updateError } = await supabase
|
||||||
|
.from('user_access')
|
||||||
|
.update({
|
||||||
|
custom_name: customName || null // Empty string becomes null
|
||||||
|
})
|
||||||
|
.eq('id', access.id);
|
||||||
|
|
||||||
|
if (updateError) {
|
||||||
|
console.error('[BENEFICIARY PATCH] Custom name update error:', updateError);
|
||||||
|
return res.status(500).json({ error: 'Failed to update custom name' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get beneficiary data for response
|
||||||
|
const { data: beneficiary } = await supabase
|
||||||
|
.from('beneficiaries')
|
||||||
|
.select('id, name, phone, address, avatar_url')
|
||||||
|
.eq('id', beneficiaryId)
|
||||||
|
.single();
|
||||||
|
|
||||||
|
const displayName = customName || beneficiary?.name || null;
|
||||||
|
|
||||||
|
console.log('[BENEFICIARY PATCH] Custom name updated:', { beneficiaryId, customName, displayName });
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
beneficiary: {
|
||||||
|
id: beneficiaryId,
|
||||||
|
name: beneficiary?.name || null,
|
||||||
|
displayName: displayName,
|
||||||
|
originalName: beneficiary?.name || null,
|
||||||
|
customName: customName || null,
|
||||||
|
phone: beneficiary?.phone || null,
|
||||||
|
address: beneficiary?.address || null,
|
||||||
|
avatarUrl: beneficiary?.avatar_url || null
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[BENEFICIARY PATCH] Error:', error);
|
console.error('[BENEFICIARY PATCH] Error:', error);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user