diff --git a/package.json b/package.json index db92c00..b83b836 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "textsender", "private": true, - "version": "0.0.0", + "version": "0.0.2", "type": "module", "scripts": { "dev": "vite", diff --git a/src/components/AddContact.css b/src/components/AddContact.css new file mode 100644 index 0000000..7ce2816 --- /dev/null +++ b/src/components/AddContact.css @@ -0,0 +1,159 @@ +/* Contact Modal Styles */ +.contact-modal-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.5); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; + padding: 20px; +} + +.contact-modal { + background: transparent; + width: 100%; + max-width: 500px; + max-height: 90vh; + overflow-y: auto; +} + +.contact-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 20px; +} + +.contact-title { + color: white; + font-size: 24px; + font-weight: 600; + margin: 0; +} + +.close-btn { + background: none; + border: none; + color: white; + font-size: 32px; + cursor: pointer; + line-height: 1; + padding: 0; + width: 32px; + height: 32px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + transition: background-color 0.3s ease; +} + +.close-btn:hover { + background-color: rgba(255, 255, 255, 0.1); +} + +.contact-card { + background: white; + border-radius: 12px; + box-shadow: 0 20px 60px rgba(0, 0, 0, 0.3); + padding: 40px; + width: 100%; +} + +.contact-form { + margin-bottom: 0; +} + +.required { + color: #ef4444; +} + +.form-actions { + display: flex; + gap: 12px; + margin-top: 30px; +} + +.cancel-btn { + flex: 1; + background: #f3f4f6; + color: #4b5563; + border: 2px solid #e5e7eb; + border-radius: 8px; + padding: 14px; + font-size: 16px; + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; +} + +.cancel-btn:hover:not(:disabled) { + background: #e5e7eb; + transform: translateY(-1px); +} + +.cancel-btn:active:not(:disabled) { + transform: translateY(0); +} + +.cancel-btn:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +.submit-btn { + flex: 1; + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + border: none; + border-radius: 8px; + padding: 14px; + font-size: 16px; + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; +} + +.submit-btn:hover:not(:disabled) { + transform: translateY(-1px); + box-shadow: 0 10px 25px rgba(102, 126, 234, 0.4); +} + +.submit-btn:active:not(:disabled) { + transform: translateY(0); +} + +.submit-btn:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +/* Responsive adjustments */ +@media (max-width: 480px) { + .contact-card { + padding: 30px 20px; + } + + .contact-title { + font-size: 20px; + } + + .form-actions { + flex-direction: column; + } +} + +.submit-error { + background-color: #fee; + border: 1px solid #fcc; + border-radius: 6px; + padding: 12px; + margin-bottom: 20px; + text-align: center; + color: #ef4444; + font-size: 14px; +} diff --git a/src/components/AddContact.jsx b/src/components/AddContact.jsx new file mode 100644 index 0000000..ce86a82 --- /dev/null +++ b/src/components/AddContact.jsx @@ -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 ( +
+
+
+

Add New Contact

+ +
+ +
+ {submitError &&
{submitError}
} + +
+
+ + + {errors.phoneNumber && ( + {errors.phoneNumber} + )} +
+ +
+ + + Optional field +
+ +
+ + + Optional field +
+ +
+ + +
+
+
+
+
+ ); +}; + +export default AddContact; diff --git a/src/components/Dashboard.css b/src/components/Dashboard.css index 30976db..54212a0 100644 --- a/src/components/Dashboard.css +++ b/src/components/Dashboard.css @@ -211,3 +211,28 @@ font-size: 18px; } } + +/* Add Contact Button Styles */ +.add-contact-btn { + background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); + color: white; + border: none; + border-radius: 8px; + padding: 12px 24px; + font-size: 16px; + font-weight: 600; + cursor: pointer; + transition: all 0.3s ease; + margin: 20px; +} + +.add-contact-btn:hover { + transform: translateY(-2px); + box-shadow: 0 10px 25px rgba(102, 126, 234, 0.4); +} + +.add-contact-btn:active { + transform: translateY(0); +} + +/* Existing dashboard styles... */ diff --git a/src/components/Dashboard.jsx b/src/components/Dashboard.jsx index 66f0249..82414b0 100644 --- a/src/components/Dashboard.jsx +++ b/src/components/Dashboard.jsx @@ -1,8 +1,25 @@ import { useState } from 'react'; + +import AddContact from './AddContact'; import './Dashboard.css'; const Dashboard = () => { const [activeView, setActiveView] = useState('dashboard'); + const [showContactForm, setShowContactForm] = useState(false); + + const handleAddContactClick = () => { + setShowContactForm(true); + }; + + const handleContactSuccess = (contactData) => { + // Optional: Handle successful creation + console.log('Contact created successfully:', contactData); + + // You could: + // 1. Refresh the contacts list if you have one + // 2. Show a success notification + // 3. Update local state with the new contact + }; const handleButtonClick = (buttonNumber) => { console.log(`Button ${buttonNumber} clicked`); @@ -58,12 +75,21 @@ const Dashboard = () => { + {/* Contact Form Modal - Now self-contained */} + setShowContactForm(false)} + onSuccess={handleContactSuccess} + /> +