diff --git a/package-lock.json b/package-lock.json index b30c02e..3882f40 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "textsender", - "version": "0.0.0", + "version": "0.0.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "textsender", - "version": "0.0.0", + "version": "0.0.3", "dependencies": { "react": "^19.2.3", "react-dom": "^19.2.3", diff --git a/package.json b/package.json index b83b836..1755665 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "textsender", "private": true, - "version": "0.0.2", + "version": "0.0.3", "type": "module", "scripts": { "dev": "vite", diff --git a/src/App.jsx b/src/App.jsx index 5b6814b..a524fa0 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -4,9 +4,11 @@ import { Route, Navigate, } from 'react-router-dom'; + import Dashboard from './components/Dashboard'; import Login from './components/Login'; import Register from './components/Register'; + import './App.css'; function App() { @@ -17,16 +19,6 @@ function App() { } /> } /> } /> - {/* Add other routes as needed */} - -

Login Page

-

This would be your login page

- - } - /> ); diff --git a/src/api/contactApi.js b/src/api/contactApi.js new file mode 100644 index 0000000..6fa2ffe --- /dev/null +++ b/src/api/contactApi.js @@ -0,0 +1,58 @@ +import { API_BASE_URL } from '../constants/api'; + +export class CreateContactRequest { + first_name = ''; + last_name = ''; + phone_number = ''; + user_id = ''; +} + +export const contactApi = { + getContactsWithUserId: async (userId, authBearerToken) => { + console.log('Fetching contacts'); + const response = await fetch( + `${API_BASE_URL}/api/v1/contact?user_id=${userId}`, + { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + Authorization: authBearerToken, + }, + } + ); + + if (!response.ok) { + if (response.status === 401) { + throw new Error('Session expired. Please log in again.'); + } else if (response.status === 404) { + console.log('No contacts associated with user'); + throw new Error('No contacts associated with user'); + } else { + throw new Error(`Failed to fetch contacts: ${response.status}`); + } + } else { + return response.json(); + } + }, + + createContact: async (contact, authBearerToken) => { + const response = await fetch(`${API_BASE_URL}/api/v1/contact/new`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: authBearerToken, + }, + body: JSON.stringify(contact), + }); + + if (!response.ok) { + const errorData = await response.json().catch(() => ({})); + throw new Error( + errorData.message || + `Failed to create contact. Status: ${response.status}` + ); + } else { + return response.json(); + } + }, +}; diff --git a/src/api/loginApi.js b/src/api/loginApi.js new file mode 100644 index 0000000..717ab4d --- /dev/null +++ b/src/api/loginApi.js @@ -0,0 +1,22 @@ +import { AUTH_API_BASE_URL } from '../constants/api'; + +export class LoginRequest { + username = ''; + password = ''; +} + +export const loginApi = { + login: async (request) => { + const reponse = await fetch(`${AUTH_API_BASE_URL}/api/v1/login`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(request), + }); + + if (reponse.ok) { + return reponse.json(); + } else { + throw new Error('Error logging in'); + } + }, +}; diff --git a/src/api/messageApi.js b/src/api/messageApi.js new file mode 100644 index 0000000..34f3596 --- /dev/null +++ b/src/api/messageApi.js @@ -0,0 +1,29 @@ +import { API_BASE_URL } from '../constants/api'; + +export class CreateMessageRequest { + content = ''; + user_id = ''; +} + +export const messageApi = { + createMessage: async (requestBody, authBearerToken) => { + const response = await fetch(`${API_BASE_URL}/api/v1/message/new`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: authBearerToken, + }, + body: JSON.stringify(requestBody), + }); + + if (!response.ok) { + if (response.status === 500) { + throw new Error('Something went wrong with the system'); + } else { + throw new Error('Failed to create message'); + } + } else { + return response.json(); + } + }, +}; diff --git a/src/api/messageEventResponseApi.js b/src/api/messageEventResponseApi.js new file mode 100644 index 0000000..bfdeab8 --- /dev/null +++ b/src/api/messageEventResponseApi.js @@ -0,0 +1,30 @@ +import { API_BASE_URL } from '../constants/api'; + +export const messageEventResponseApi = { + getMessageEventResponsesByUserId: async (userId, authBearerToken) => { + const response = await fetch( + `${API_BASE_URL}/api/v1/schedule/message/event/response?user_id=${userId}`, + { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + Authorization: authBearerToken, + }, + } + ); + + if (!response.ok) { + if (response.status === 401) { + throw new Error('Session expired. Please log in again.'); + } else if (response.status === 404) { + console.log('No message event responses associated with user'); + // setLoading(false); + return; + } else { + throw new Error(`Failed to fetch sent messages: ${response.status}`); + } + } else { + return response.json(); + } + }, +}; diff --git a/src/api/registerApi.js b/src/api/registerApi.js new file mode 100644 index 0000000..e013e11 --- /dev/null +++ b/src/api/registerApi.js @@ -0,0 +1,24 @@ +import { AUTH_API_BASE_URL } from '../constants/api'; + +export class RegisterRequest { + phone_number = ''; + firstname = ''; + password = ''; +} + +export const registerApi = { + registerUser: async (request) => { + const response = await fetch(`${AUTH_API_BASE_URL}/api/v1/register`, { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(request), + }); + + if (response.ok) { + return response.json(); + } else { + console.error('Error registering'); + throw new Error('Registration failed'); + } + }, +}; diff --git a/src/api/sendMessageApi.js b/src/api/sendMessageApi.js new file mode 100644 index 0000000..15f2ae6 --- /dev/null +++ b/src/api/sendMessageApi.js @@ -0,0 +1,26 @@ +import { API_BASE_URL } from '../constants/api'; + +export class SendInstantMessageRequest { + contact_ids = []; + message_id = ''; + user_id = ''; +} + +export const sendMessageApi = { + sendInstantMessage: async (requestBody, authBearerToken) => { + const response = await fetch(`${API_BASE_URL}/api/v1/instant/message`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + Authorization: authBearerToken, + }, + body: JSON.stringify(requestBody), + }); + + if (!response.ok) { + throw new Error('Failed to send instant message'); + } else { + return response.json(); + } + }, +}; diff --git a/src/components/AddContact.jsx b/src/components/AddContact.jsx index 6408777..c0784e3 100644 --- a/src/components/AddContact.jsx +++ b/src/components/AddContact.jsx @@ -1,11 +1,8 @@ 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 { contactApi, CreateContactRequest } from '../api/contactApi'; +import { tokenService } from '../services/TokenService'; +import { DATA_KEY_ADDED_CONTACT_ID } from '../constants/app'; import './AddContact.css'; @@ -66,33 +63,16 @@ const AddContact = ({ isOpen, onClose, onSuccess }) => { 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(); + const userId = tokenService.getUserId(); + let contact = new CreateContactRequest(); + contact.phone_number = formData.phoneNumber; + contact.first_name = formData.firstName; + contact.last_name = formData.lastName; + contact.user_id = userId; + const result = await contactApi.createContact( + contact, + tokenService.bearerToken() + ); if (result.data.length > 0) { console.log('Contact added'); const contactId = result.data[0].id; diff --git a/src/components/Dashboard.jsx b/src/components/Dashboard.jsx index 39492ad..cb22e4a 100644 --- a/src/components/Dashboard.jsx +++ b/src/components/Dashboard.jsx @@ -5,11 +5,13 @@ import ViewContact from './ViewContact'; import ViewSentMessages from './ViewSentMessages'; import './Dashboard.css'; +import SendMessageWizardModal from './SendMessageWizard/SendMessageWizardModal'; const Dashboard = () => { const [activeView, setActiveView] = useState('dashboard'); - const [showContactForm, setShowContactForm] = useState(false); + const [isWizardOpen, setIsWizardOpen] = useState(false); const [isViewSentMessagesOpen, setViewSentMessagesOpen] = useState(false); + const [showContactForm, setShowContactForm] = useState(false); const [isPopupOpen, setIsPopupOpen] = useState(false); const handleAddContactClick = () => { @@ -25,9 +27,13 @@ const Dashboard = () => { // 3. Update local state with the new contact }; - const handleButtonClick = (buttonNumber) => { - console.log(`Button ${buttonNumber} clicked`); + const handleWizardComplete = () => { // Add your logic here for each button + setIsWizardOpen(false); + }; + + const handleWizardClose = () => { + setIsWizardOpen(false); }; const handleNavClick = (view) => { @@ -71,12 +77,18 @@ const Dashboard = () => {
+ + +
+ ); + } + + return ( +
+
+
+ setSearchTerm(e.target.value)} + className={styles.searchInput} + /> +
+ +
+ + + {selectedContacts.length} of {contacts.length} selected + +
+
+ +
+ {filteredContacts.length === 0 ? ( +

No contacts found

+ ) : ( + filteredContacts.map((contact) => ( +
onToggleContact(contact.id)} + > +
+ {}} + readOnly + /> +
+
+ + {contact.phone_number} + + + {contact.first_name} + +
+
+ )) + )} +
+
+ ); +} + +export default ContactSelection; diff --git a/src/components/SendMessageWizard/MessageForm.jsx b/src/components/SendMessageWizard/MessageForm.jsx new file mode 100644 index 0000000..8e81591 --- /dev/null +++ b/src/components/SendMessageWizard/MessageForm.jsx @@ -0,0 +1,104 @@ +import React, { useState } from 'react'; +import styles from './SendMessageWizard.module.css'; +import { tokenService } from '../../services/TokenService'; +import { messageApi, CreateMessageRequest } from '../../api/messageApi'; + +function MessageForm({ message, onMessageChange, onMessageCreated }) { + const [characterCount, setCharacterCount] = useState(message.length); + const [isCreatingMessage, setIsCreatingMessage] = useState(false); + const [createdMessage, setCreatedMessage] = useState(null); + + const handleMessageChange = (e) => { + const newMessage = e.target.value; + onMessageChange(newMessage); + setCharacterCount(newMessage.length); + }; + + const createMessage = async () => { + if (!message.trim()) { + return; + } + + setIsCreatingMessage(true); + setCreatedMessage(null); + + try { + const userId = tokenService.getUserId(); + let reqBod = new CreateMessageRequest(); + reqBod.user_id = userId; + reqBod.content = message; + const response = await messageApi.createMessage( + reqBod, + tokenService.bearerToken() + ); + + if (!response) { + throw new Error('Failed to create message'); + } else { + const createdMessage = response.data[0]; + setCreatedMessage(createdMessage); + if (onMessageCreated) { + console.log('Setting createdMessage'); + onMessageCreated(createdMessage); + } + console.log('Created message: ', createdMessage); + } + } catch (error) { + console.error(error); + } finally { + setIsCreatingMessage(false); + } + }; + + return ( +
+
+ +