tsk-5: Add contact (#15)

Closes #5

Reviewed-on: phoenix/textsender#15
Co-authored-by: phoenix <kundeng00@pm.me>
Co-committed-by: phoenix <kundeng00@pm.me>
This commit is contained in:
phoenix
2025-12-21 00:27:20 +00:00
committed by phoenix
parent 6514f290d7
commit 6f952dc8df
8 changed files with 443 additions and 5 deletions
+224
View File
@@ -0,0 +1,224 @@
import { useState } from 'react';
import { API_BASE_URL } from '../constants/api';
import {
DATA_KEY_ACCESS_TOKEN,
DATA_KEY_ADDED_CONTACT_ID,
DATA_KEY_USER_ID,
} from '../constants/app';
import './AddContact.css';
const AddContact = ({ isOpen, onClose, onSuccess }) => {
const [formData, setFormData] = useState({
phoneNumber: '',
firstName: '',
lastName: '',
});
const [errors, setErrors] = useState({});
const [isSubmitting, setIsSubmitting] = useState(false);
const [submitError, setSubmitError] = useState('');
if (!isOpen) return null;
const handleChange = (e) => {
const { name, value } = e.target;
setFormData((prev) => ({
...prev,
[name]: value,
}));
// Clear error when user starts typing
if (errors[name]) {
setErrors((prev) => ({
...prev,
[name]: '',
}));
}
// Clear submit error when user modifies form
if (submitError) {
setSubmitError('');
}
};
const validateForm = () => {
const newErrors = {};
// Phone number is required
if (!formData.phoneNumber.trim()) {
newErrors.phoneNumber = 'Phone number is required';
} else if (!/^[\d\s\-\+\(\)]{10,}$/.test(formData.phoneNumber)) {
newErrors.phoneNumber = 'Please enter a valid phone number';
}
return newErrors;
};
const handleSubmit = async (e) => {
e.preventDefault();
const validationErrors = validateForm();
if (Object.keys(validationErrors).length > 0) {
setErrors(validationErrors);
return;
}
setIsSubmitting(true);
setSubmitError('');
try {
const userId = localStorage.getItem(DATA_KEY_USER_ID);
const accessToken = localStorage.getItem(DATA_KEY_ACCESS_TOKEN);
const reqBod = {
phone_number: formData.phoneNumber,
first_name: formData.firstName,
last_name: formData.lastName,
user_id: userId,
};
// Send the API request directly from the Contact component
const response = await fetch(`${API_BASE_URL}/api/v1/contact/new`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${accessToken}`,
},
body: JSON.stringify(reqBod),
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(
errorData.message ||
`Failed to create contact. Status: ${response.status}`
);
}
const result = await response.json();
if (result.data.length > 0) {
console.log('Contact added');
const contactId = result.data[0].id;
localStorage.setItem(DATA_KEY_ADDED_CONTACT_ID, contactId);
}
// Reset form
setFormData({ phoneNumber: '', firstName: '', lastName: '' });
// Call onSuccess callback if provided
if (onSuccess) {
onSuccess(result);
}
// Close the modal
onClose();
} catch (error) {
console.error('Error submitting form:', error);
setSubmitError(
error.message || 'Failed to create contact. Please try again.'
);
} finally {
setIsSubmitting(false);
}
};
const handleCancel = () => {
setFormData({ phoneNumber: '', firstName: '', lastName: '' });
setErrors({});
setSubmitError('');
onClose();
};
return (
<div className="contact-modal-overlay">
<div className="contact-modal">
<div className="contact-header">
<h2 className="contact-title">Add New Contact</h2>
<button
className="close-btn"
onClick={handleCancel}
aria-label="Close"
>
×
</button>
</div>
<div className="contact-card">
{submitError && <div className="submit-error">{submitError}</div>}
<form className="contact-form" onSubmit={handleSubmit}>
<div className="form-group">
<label htmlFor="phoneNumber" className="form-label">
Phone Number <span className="required">*</span>
</label>
<input
id="phoneNumber"
name="phoneNumber"
type="tel"
className={`form-input ${errors.phoneNumber ? 'error' : ''}`}
value={formData.phoneNumber}
onChange={handleChange}
placeholder="Enter phone number"
disabled={isSubmitting}
required
/>
{errors.phoneNumber && (
<span className="error-message">{errors.phoneNumber}</span>
)}
</div>
<div className="form-group">
<label htmlFor="firstName" className="form-label">
First Name
</label>
<input
id="firstName"
name="firstName"
type="text"
className="form-input"
value={formData.firstName}
onChange={handleChange}
placeholder="Enter first name (optional)"
disabled={isSubmitting}
/>
<span className="form-hint">Optional field</span>
</div>
<div className="form-group">
<label htmlFor="lastName" className="form-label">
Last Name
</label>
<input
id="lastName"
name="lastName"
type="text"
className="form-input"
value={formData.lastName}
onChange={handleChange}
placeholder="Enter last name (optional)"
disabled={isSubmitting}
/>
<span className="form-hint">Optional field</span>
</div>
<div className="form-actions">
<button
type="button"
className="cancel-btn"
onClick={handleCancel}
disabled={isSubmitting}
>
Cancel
</button>
<button
type="submit"
className="submit-btn"
disabled={isSubmitting}
>
{isSubmitting ? 'Creating...' : 'Create Contact'}
</button>
</div>
</form>
</div>
</div>
</div>
);
};
export default AddContact;