From 3a09d77973ef871710066a2ce05ae29199b6cedb Mon Sep 17 00:00:00 2001 From: cdl431 Date: Mon, 20 Oct 2025 16:42:19 -0500 Subject: [PATCH 1/5] Added an edit function --- server/controllers/authController.js | 51 ++++++++++++++++++++++++---- server/routes/authRoutes.js | 2 +- 2 files changed, 46 insertions(+), 7 deletions(-) diff --git a/server/controllers/authController.js b/server/controllers/authController.js index e9037fa..7962d04 100644 --- a/server/controllers/authController.js +++ b/server/controllers/authController.js @@ -71,20 +71,59 @@ const login = async(req,res) => { } }; -const deleteByUser = async (req,res) => { +const editByUser = async (req, res) => { + try { + const { id } = req.params; + const { username, email, password } = req.body; + + const user = await User.findById(id); + if (!user) { + return res.status(404).json({ error: 'User not found' }); + } + + const updatedFields = {}; + + if (username) updatedFields.username = username; + if (email) { + const existingUser = await User.findByEmail(email); + if (existingUser && existingUser.id !== parseInt(id)) { + return res.status(400).json({ error: 'Email already in use' }); + } + updatedFields.email = email; + } + if (password) { + const saltRounds = 12; + const salt = await bcrypt.genSalt(saltRounds); + const hashedPassword = await bcrypt.hash(password, salt); + updatedFields.password = hashedPassword; + } + + const updatedUser = await User.updateUser(id, updatedFields); + + res.status(200).json({ + message: 'User updated successfully', + user: updatedUser + }); + } catch (error) { + console.error(error); + res.status(500).json({ error: 'Error updating user' }); + } +}; + +async function deleteByUser(req, res) { const { id } = req.params; - try{ + try { await User.deleteUser(id); - res.json({ + res.json({ message: "User deleted successfullyS" - }) + }); } - catch (error){ + catch (error) { console.error(error); - res.status(500);json({error:"Error deleting user"}) + res.status(500); json({ error: "Error deleting user" }); } } diff --git a/server/routes/authRoutes.js b/server/routes/authRoutes.js index a14ab28..3121695 100644 --- a/server/routes/authRoutes.js +++ b/server/routes/authRoutes.js @@ -2,7 +2,7 @@ const express = require('express'); const router = express.Router(); -const { register, login, deleteByUser, findByEmail, findById, getAllUsers } = require('../controllers/authController'); // Assuming you have a controller for your registration logic +const { register, login, deleteByUser, findByEmail, findById, getAllUsers, } = require('../controllers/authController'); // Assuming you have a controller for your registration logic console.log('Register function:', register); From f5e554f038c56fd2af03e9a60c2a370d5c12d24a Mon Sep 17 00:00:00 2001 From: cdl431 Date: Mon, 3 Nov 2025 17:57:34 -0600 Subject: [PATCH 2/5] Added an edit folder to client:component --- client/src/components/edit/edit.js | 17 +++++ client/src/components/edit/popup.css | 5 ++ client/src/components/edit/styles.js | 99 ++++++++++++++++++++++++++++ server/routes/treeMemberRoute.js | 2 +- 4 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 client/src/components/edit/edit.js create mode 100644 client/src/components/edit/popup.css create mode 100644 client/src/components/edit/styles.js diff --git a/client/src/components/edit/edit.js b/client/src/components/edit/edit.js new file mode 100644 index 0000000..270c28d --- /dev/null +++ b/client/src/components/edit/edit.js @@ -0,0 +1,17 @@ +import React, { useState, useEffect, useRef, useMemo } from 'react'; +import { Link } from 'react-router-dom'; +import Popup from 'reactjs-popup'; +import 'reactjs-popup/dist/index.css'; +import { useForm } from 'react-hook-form'; +import * as styles from './styles'; +import './popup.css'; +import { ReactComponent as CloseIcon } from '../../assets/exit.svg'; +import { ReactComponent as ImportIcon } from '../../assets/import.svg'; +import { useCurrentUser } from '../../CurrentUserProvider'; +import { editTreeMember } from '../../../../server/controllers/treeMemberController'; + +Function editTreeMember({ trigger, userid }) { + + + +} \ No newline at end of file diff --git a/client/src/components/edit/popup.css b/client/src/components/edit/popup.css new file mode 100644 index 0000000..c230a6a --- /dev/null +++ b/client/src/components/edit/popup.css @@ -0,0 +1,5 @@ +.popup-content { + width: auto; + border-radius: 30px; + min-width: 0px; +} \ No newline at end of file diff --git a/client/src/components/edit/styles.js b/client/src/components/edit/styles.js new file mode 100644 index 0000000..80059fe --- /dev/null +++ b/client/src/components/edit/styles.js @@ -0,0 +1,99 @@ +export const DefaultStyle = { + fontFamily: 'Alata', +}; + +export const FieldStyle = { + borderRadius: '5px', + border: '1px solid #000000', + marginLeft: '10px' +}; + +export const ListStyle = { + listStyleType: 'none', + fontFamily: 'Alata', + display: 'flex', + flexDirection: 'column', + alignItems: 'flex-end', + marginRight: '15%' +}; + +export const ButtonDivStyle = { + fontFamily: 'Alata', + display: 'flex', + justifyContent: 'center', +} + +export const ButtonStyle = { + fontFamily: 'Alata', + backgroundColor: '#3a5a40', + color: 'white', + borderRadius: '20px', + border: 'none', + padding: '10px 30px', + margin: '10px', + cursor: 'pointer', +} + +export const GrayButtonStyle = { + fontFamily: 'Alata', + backgroundColor: '#D9D9D9', + color: 'black', + borderRadius: '20px', + border: 'none', + padding: '10px 20px', + margin: '10px', + cursor: 'pointer', + display: 'flex', + flexDirection: 'row', + boxShadow: 'gray 0px 10px 10px -8px', +} + +export const FormStyle = { + padding: '2vw', + paddingTop: '0px', + minWidth: '360px', +} + +export const ItemStyle = { + margin: '10px 0px' +} + +export const DateFieldStyle = { + borderRadius: '5px', + border: '1px solid #000000', + marginLeft: '10px', + width: '147px', + fontFamily: 'Alata' +}; + + +export const MainContainerStyle = { + display: 'flex', + flexDirection: 'column', + padding: '2vw', + paddingTop: '0px', + alignItems: 'center', + minWidth: '360px', + minHeight: '150px', + justifyContent: 'space-between', +} + +export const AddOptionsStyle = { + display: 'flex', + flexDirection: 'column', + alignItems: 'flex-start', + width: '90%', + padding: '10px', + fontFamily: 'Alata', + // border: '1px solid gray', + // borderRadius: '5px', + marginTop: '10px', + height: '200px', + overflow: 'auto' +} + +export const ListingStyle = { + padding: '10px', + border: '1px solid gray', + width: '90%' +} \ No newline at end of file diff --git a/server/routes/treeMemberRoute.js b/server/routes/treeMemberRoute.js index 7c45829..5435d13 100644 --- a/server/routes/treeMemberRoute.js +++ b/server/routes/treeMemberRoute.js @@ -2,7 +2,7 @@ const express = require('express'); const router = express.Router(); -const { addTreeMember, editTreeMember,getMembersByUser, getMembersByOtherUser, deleteByUser, getMemberById, getActiveMemberId } = require('../controllers/treeMemberController'); +const { addTreeMember, editTreeMember,getMembersByUser, getMembersByOtherUser, deleteByUser, getMemberById, getActiveMemberId} = require('../controllers/treeMemberController'); router.post('/', addTreeMember); router.put('/:id', editTreeMember); From 7cfd62d56c08f3e1c74f4edf993819d11bcef546 Mon Sep 17 00:00:00 2001 From: cdl431 Date: Mon, 10 Nov 2025 16:22:20 -0600 Subject: [PATCH 3/5] Finished code for EditFamilyMember function --- .../EditFamilyMember/EditFamilyMember.js | 254 ++++++++++++++++++ .../{edit => EditFamilyMember}/popup.css | 0 .../{edit => EditFamilyMember}/styles.js | 0 client/src/components/edit/edit.js | 17 -- 4 files changed, 254 insertions(+), 17 deletions(-) create mode 100644 client/src/components/EditFamilyMember/EditFamilyMember.js rename client/src/components/{edit => EditFamilyMember}/popup.css (100%) rename client/src/components/{edit => EditFamilyMember}/styles.js (100%) delete mode 100644 client/src/components/edit/edit.js diff --git a/client/src/components/EditFamilyMember/EditFamilyMember.js b/client/src/components/EditFamilyMember/EditFamilyMember.js new file mode 100644 index 0000000..2d4369c --- /dev/null +++ b/client/src/components/EditFamilyMember/EditFamilyMember.js @@ -0,0 +1,254 @@ +import React, { useState, useEffect, useMemo } from 'react'; +import Popup from 'reactjs-popup'; +import 'reactjs-popup/dist/index.css'; +import { useForm } from 'react-hook-form'; +import * as styles from './styles'; +import './popup.css'; +import { ReactComponent as CloseIcon } from '../../assets/exit.svg'; +import { useCurrentUser } from '../../CurrentUserProvider'; + +function EditFamilyMemberPopup({ trigger, memberId, onSaved }) { + const { currentUserID } = useCurrentUser(); + + const [loading, setLoading] = useState(true); + const [view, setView] = useState("basic"); + + const [memberData, setMemberData] = useState(null); + const [relationshipData, setRelationshipData] = useState(null); + + const matPat = useMemo(() => ["parent", "cousin", "aunt", "uncle", "grandparent", "niece", "nephew"], []); + + + + const { + register, + handleSubmit, + reset + } = useForm(); + + const { + register: registerRel, + handleSubmit: handleSubmitRel, + reset: resetRel + } = useForm(); + + + + useEffect(() => { + const load = async () => { + try { + setLoading(true); + + const memberRes = await fetch(`http://localhost:5000/api/family-members/${memberId}`); + const memberJson = await memberRes.json(); + + const relRes = await fetch(`http://localhost:5000/api/relationships/member/${memberId}`); + const relJson = await relRes.json(); + + setMemberData(memberJson); + setRelationshipData(relJson); + + // preload forms + reset({ + firstName: memberJson.firstName, + lastName: memberJson.lastName, + location: memberJson.location, + birthday: memberJson.birthDate, + birthplace: memberJson.birthplace, + deathdate: memberJson.deathDate + }); + + resetRel({ + relationship: relJson.relationshipType, + matPat: relJson.side + }); + + } catch (err) { + console.error(err); + } + + setLoading(false); + }; + + if (memberId) load(); + }, [memberId, reset, resetRel]); + + // ----------- CLOSE HANDLER ----------- + + const closeModal = (close) => { + reset(); + resetRel(); + close(); + }; + + // ----------- SAVE BASIC INFO ----------- + + const onSubmitBasic = async (data) => { + try { + const res = await fetch(`http://localhost:5000/api/family-members/${memberId}`, { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + firstName: data.firstName, + lastName: data.lastName, + location: data.location || null, + birthDate: data.birthday || null, + birthplace: data.birthplace || null, + deathDate: data.deathdate || null + }) + }); + + if (res.ok) onSaved && onSaved(); + } catch (err) { + console.error(err); + } + }; + + + + const onSubmitRelationship = async (data) => { + try { + const res = await fetch(`http://localhost:5000/api/relationships/${relationshipData.id}`, { + method: "PUT", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + relationshipType: data.relationship, + side: matPat.includes(data.relationship) ? data.matPat : null, + relationshipStatus: "active" + }) + }); + + if (res.ok) onSaved && onSaved(); + + } catch (err) { + console.error(err); + } + }; + + if (loading) return null; + + return ( + + {(close) => ( +
+ + {/* Close button */} +
+ +
+ +

+ Edit Family Member +

+ + {/* Tab Selector */} +
+ + +
+ + {/* BASIC INFO FORM */} + {view === "basic" && ( +
+
    +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+ +
+ +
+
+ )} + + {/* RELATIONSHIP FORM */} + {view === "relationship" && ( +
+
    +
  • + +
  • + + {/* Maternal/paternal only shown when needed */} + {matPat.includes(relationshipData.relationshipType) && ( +
  • + Maternal + Paternal +
  • + )} +
+ +
+ +
+
+ )} + +
+ )} +
+ ); +} + +export default EditFamilyMemberPopup; \ No newline at end of file diff --git a/client/src/components/edit/popup.css b/client/src/components/EditFamilyMember/popup.css similarity index 100% rename from client/src/components/edit/popup.css rename to client/src/components/EditFamilyMember/popup.css diff --git a/client/src/components/edit/styles.js b/client/src/components/EditFamilyMember/styles.js similarity index 100% rename from client/src/components/edit/styles.js rename to client/src/components/EditFamilyMember/styles.js diff --git a/client/src/components/edit/edit.js b/client/src/components/edit/edit.js deleted file mode 100644 index 270c28d..0000000 --- a/client/src/components/edit/edit.js +++ /dev/null @@ -1,17 +0,0 @@ -import React, { useState, useEffect, useRef, useMemo } from 'react'; -import { Link } from 'react-router-dom'; -import Popup from 'reactjs-popup'; -import 'reactjs-popup/dist/index.css'; -import { useForm } from 'react-hook-form'; -import * as styles from './styles'; -import './popup.css'; -import { ReactComponent as CloseIcon } from '../../assets/exit.svg'; -import { ReactComponent as ImportIcon } from '../../assets/import.svg'; -import { useCurrentUser } from '../../CurrentUserProvider'; -import { editTreeMember } from '../../../../server/controllers/treeMemberController'; - -Function editTreeMember({ trigger, userid }) { - - - -} \ No newline at end of file From 2342e928041d87244eac5b0dbc8709dbb1eb7e0f Mon Sep 17 00:00:00 2001 From: cdl431 Date: Mon, 6 Apr 2026 12:49:58 -0500 Subject: [PATCH 4/5] Frontend Syncs Contacts Option Added a option under the profile in the settings to sync contacts --- client/src/pages/SyncContacts/SyncContacts.js | 67 +++++++++++++++++++ .../src/pages/SyncContacts/SyncContatcs.css | 5 ++ client/src/pages/SyncContacts/styles.js | 39 +++++++++++ 3 files changed, 111 insertions(+) create mode 100644 client/src/pages/SyncContacts/SyncContacts.js create mode 100644 client/src/pages/SyncContacts/SyncContatcs.css create mode 100644 client/src/pages/SyncContacts/styles.js diff --git a/client/src/pages/SyncContacts/SyncContacts.js b/client/src/pages/SyncContacts/SyncContacts.js new file mode 100644 index 0000000..eb12fcf --- /dev/null +++ b/client/src/pages/SyncContacts/SyncContacts.js @@ -0,0 +1,67 @@ +import { React, useState } from 'react'; +import * as styles from './styles'; + +function SyncContacts() { + const [contacts, setContacts] = useState([]); + const [errorMessage, setErrorMessage] = useState(""); + const [loading, setLoading] = useState(false); + + const handleSyncContacts = () => { + setLoading(true); + + fetch('http://localhost:5000/api/contacts/sync', { + method: 'GET', + headers: { 'Content-Type': 'application/json' } + }) + .then(async (response) => { + if (response.ok) { + const data = await response.json(); + setContacts(data); + console.log("Contacts synced:", data); + } else { + const errorData = await response.json(); + console.error('Error:', errorData.message); + setErrorMessage(errorData.message); + } + }) + .catch(error => { + console.error('There was a problem with the fetch operation:', error); + setErrorMessage("Failed to sync contacts"); + }) + .finally(() => { + setLoading(false); + }); + }; + + return ( +
+

Sync Contacts

+ + {/* Sync Button */} + + + {/* Error Message */} + {errorMessage && ( +

+ {errorMessage} +

+ )} + + {/* Contacts List */} +
    + {contacts.map((contact, index) => ( +
  • + {contact.name} - {contact.email} +
  • + ))} +
+
+ ); +} + +export default SyncContacts; \ No newline at end of file diff --git a/client/src/pages/SyncContacts/SyncContatcs.css b/client/src/pages/SyncContacts/SyncContatcs.css new file mode 100644 index 0000000..8c5a989 --- /dev/null +++ b/client/src/pages/SyncContacts/SyncContatcs.css @@ -0,0 +1,5 @@ +.home-page { + display: flex; + text-align: center; + color: black; +} \ No newline at end of file diff --git a/client/src/pages/SyncContacts/styles.js b/client/src/pages/SyncContacts/styles.js new file mode 100644 index 0000000..470ea5b --- /dev/null +++ b/client/src/pages/SyncContacts/styles.js @@ -0,0 +1,39 @@ +// Sync Contacts Styles + +export const SyncContainer = { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + marginTop: '20px', + width: '100%' +}; + +export const SyncButton = { + padding: '10px 20px', + marginTop: '10px', + borderRadius: '8px', + border: 'none', + backgroundColor: '#bbbbbb', + color: 'white', + cursor: 'pointer' +}; + +export const SyncList = { + marginTop: '20px', + width: '80%', + listStyleType: 'none', + padding: '0' +}; + +export const SyncItem = { + padding: '10px', + borderBottom: '1px solid #000000', + textAlign: 'left', + width: '100%' +}; + +export const ErrorText = { + color: 'red', + marginTop: '10px', + textAlign: 'center' +}; \ No newline at end of file From 22fe529958cfa87580949a2693f378b886e687ab Mon Sep 17 00:00:00 2001 From: cdl431 Date: Sun, 26 Apr 2026 13:06:37 -0500 Subject: [PATCH 5/5] SyncContacts updated backend data --- server/controllers/contactController.js | 36 ++++++++++++++++++++ server/models/Contacts.js | 45 +++++++++++++++++++++++++ server/routes/ContactRoutes.js | 28 +++++++++++++++ 3 files changed, 109 insertions(+) create mode 100644 server/controllers/contactController.js create mode 100644 server/models/Contacts.js create mode 100644 server/routes/ContactRoutes.js diff --git a/server/controllers/contactController.js b/server/controllers/contactController.js new file mode 100644 index 0000000..395dde2 --- /dev/null +++ b/server/controllers/contactController.js @@ -0,0 +1,36 @@ +const Contact = require("../models/Contacts"); + + +exports.syncContacts = async (req, res) => { + try { + const userId = req.user.id; + const { contacts } = req.body; + + if (!contacts || !Array.isArray(contacts)) { + return res.status(400).json({ + message: "Contacts must be an array" + }); + } + + // Attach user_id to each contact before saving + const formattedContacts = contacts.map((c) => ({ + user_id: userId, + name: c.name, + phone: c.phone, + email: c.email || null, + relationship: c.relationship || null + })); + + const result = await Contact.create(formattedContacts); + + res.status(200).json({ + message: "Contacts synced successfully", + data: result + }); + } catch (error) { + res.status(500).json({ + message: "Error syncing contacts", + error: error.message + }); + } +}; \ No newline at end of file diff --git a/server/models/Contacts.js b/server/models/Contacts.js new file mode 100644 index 0000000..caa326a --- /dev/null +++ b/server/models/Contacts.js @@ -0,0 +1,45 @@ +const db = require("../db/knex"); + +const Contacts = { + + addContact: async (data) => { + return db("contacts").insert(data); + }, + + addMultipleContacts: async (contacts) => { + return db("contacts").insert(contacts); + }, + + getContactsByUser: async (userId) => { + return db("contacts") + .where("user_id", userId) + .select("*"); + }, + + getContactById: async (id) => { + return db("contacts") + .where("id", id) + .first(); + }, + + updateContact: async (id, data) => { + return db("contacts") + .where("id", id) + .update(data); + }, + + deleteContact: async (id) => { + return db("contacts") + .where("id", id) + .del(); + }, + + deleteByUser: async (userId) => { + return db("contacts") + .where("user_id", userId) + .del(); + } + +}; + +module.exports = Contacts; \ No newline at end of file diff --git a/server/routes/ContactRoutes.js b/server/routes/ContactRoutes.js new file mode 100644 index 0000000..f65fbb2 --- /dev/null +++ b/server/routes/ContactRoutes.js @@ -0,0 +1,28 @@ +const express = require('express'); +const router = express.Router(); + +const { + addContact, + addMultipleContacts, + getContactsByUser, + getContactById, + updateContact, + deleteContact, + deleteByUser +} = require('../controllers/contactController'); + +router.post('/add', addContact); + +router.post('/add-multiple', addMultipleContacts); + +router.get('/user/:userId', getContactsByUser); + +router.get('/:id', getContactById); + +router.put('/:id', updateContact); + +router.delete('/:id', deleteContact); + +router.delete('/user/:userId', deleteByUser); + +module.exports = router; \ No newline at end of file