diff --git a/src/app/components/PostAccountUpdateSidebar.js b/src/app/components/PostAccountUpdateSidebar.js
new file mode 100644
index 0000000..6410dd5
--- /dev/null
+++ b/src/app/components/PostAccountUpdateSidebar.js
@@ -0,0 +1,62 @@
+import React from 'react';
+import PropTypes from 'prop-types';
+import { createPortal } from 'react-dom';
+import TransitionOverlay from './TransitionOverlay';
+
+function AccountUpdateSuccessContent({ onClose }) {
+ return (
+ <>
+
Settings Updated!
+
Please refresh the page to see latest changes.
+
+
+
+ >
+ );
+}
+
+AccountUpdateSuccessContent.propTypes = {
+ onClose: PropTypes.func.isRequired,
+};
+
+function AccountUpdateErrorContent({ onClose, error }) {
+ return (
+ <>
+
Settings Update Error
+
{error}
+
+
+
+ >
+ );
+}
+
+AccountUpdateErrorContent.propTypes = {
+ onClose: PropTypes.func.isRequired,
+ error: PropTypes.string.isRequired,
+};
+
+const PostAccountUpdateSidebar = ({ onClose, accountUpdateError }) => {
+ return createPortal(
+
+ {accountUpdateError ? (
+
+ ) : (
+
+ )}
+ ,
+ document.body,
+ );
+};
+
+export default PostAccountUpdateSidebar;
diff --git a/src/app/components/TradesSidebar.js b/src/app/components/TradesByDateSidebar.js
similarity index 97%
rename from src/app/components/TradesSidebar.js
rename to src/app/components/TradesByDateSidebar.js
index 570d302..9226a1e 100644
--- a/src/app/components/TradesSidebar.js
+++ b/src/app/components/TradesByDateSidebar.js
@@ -133,13 +133,13 @@ Content.propTypes = {
onClose: PropTypes.func.isRequired,
};
-const TradesSidebar = ({ date = '2024-12-21', trades = [], onClose }) => {
+const TradesByDateSidebar = ({ date = '2024-12-21', trades = [], onClose }) => {
return createPortal(
,
- document.body, // Render modal into the body
+ document.body,
);
};
-export default TradesSidebar;
+export default TradesByDateSidebar;
diff --git a/src/app/components/TransitionOverlay.js b/src/app/components/TransitionOverlay.js
index 40a8379..de62321 100644
--- a/src/app/components/TransitionOverlay.js
+++ b/src/app/components/TransitionOverlay.js
@@ -6,7 +6,6 @@ const TransitionOverlay = ({ onClose, children }) => {
const entryTimerRef = useRef(null);
const exitTimerRef = useRef(null);
- // Trigger visibility state change after the component mounts
useEffect(() => {
entryTimerRef.current = setTimeout(() => {
setIsVisible(true);
@@ -17,9 +16,8 @@ const TransitionOverlay = ({ onClose, children }) => {
clearTimeout(entryTimerRef.current);
}
};
- }, []); // This runs when the modal is first opened
+ }, []);
- // Handling Scroll Locking (On Mount and Cleanup)
useEffect(() => {
document.body.classList.add('overflow-hidden');
return () => {
@@ -27,18 +25,13 @@ const TransitionOverlay = ({ onClose, children }) => {
};
}, []);
- // Handle the visibility transition (and call onClose after exit animation)
const handleClose = () => {
- // Trigger exit animation by setting visibility to false
setIsVisible(false);
-
- // After the exit animation ends (500ms), call onClose to remove the modal
exitTimerRef.current = setTimeout(() => {
onClose();
- }, 150); // Duration should match the transition duration
+ }, 150);
};
- // Cleanup timeout when the modal is unmounted or handleClose is triggered again
useEffect(() => {
return () => {
if (exitTimerRef.current) {
diff --git a/src/app/dashboard/page.js b/src/app/dashboard/page.js
index 3bce665..c54698a 100644
--- a/src/app/dashboard/page.js
+++ b/src/app/dashboard/page.js
@@ -1,138 +1,99 @@
'use client';
-import { useEffect, useState } from 'react';
+import { useState } from 'react';
import TradingCalendarView from '../components/CustomCalendar';
import TradingCalendarViewMobile from '../components/CustomCalendarMobile';
import AddNewTradeBtn from '../components/AddNewTradeBtn';
-import AddNewTradeFormModal from '../components/AddNewTradeFormModal';
+import AddNewTradeFormSidebar from '../components/AddNewTradeFormSidebar';
import { MOCK_TRADE_SUBMISSION, MOCK_TRADES_SIMPLE } from '../data/mock-trades';
import EditTradeBtn from '../components/EditTradeBtn';
-import EditTradeFormModal from '../components/EditTradeFormModal';
-import TradesSidebar from '../components/TradesSidebar';
-import { useUser } from '@clerk/nextjs';
-// import { doc, getDoc, setDoc } from 'firebase/firestore';
-// import { FIREBASE_DB } from '../../services/firebase/config';
-// import { getData } from '@/services/firebase/getData';
-import { fetchUserById, saveNewUser } from '@/utils/users';
-// import { useAuth, useUser } from '@clerk/nextjs';
+import EditTradeFormSidebar from '../components/EditTradeFormSidebar';
+import TradesByDateSidebar from '../components/TradesByDateSidebar';
+import { useUserDetails } from '../hooks/useUserDetails';
+import { useIsMobile } from '../hooks/useIsMobile';
const DashboardPage = () => {
- const [isMobile, setIsMobile] = useState(false);
- const [isAddNewTradeFormModalOpen, setIsAddNewTradeFormModalOpen] = useState(false);
- const [isEditTradeFormModalOpen, setIsEditTradeFormModalOpen] = useState(false);
+ const isMobile = useIsMobile(768);
+ const {
+ userDetails,
+ isLoading: isLoadingUserDetails,
+ error: userDetailsError,
+ } = useUserDetails();
+
+ const [isAddNewTradeFormSidebarOpen, setIsAddNewTradeFormSidebarOpen] = useState(false);
+ const [isEditTradeFormSidebarOpen, setIsEditTradeFormSidebarOpen] = useState(false);
const [isTradesSidebarOpen, setIsTradesSidebarOpen] = useState(false);
- const [selectedDate, setSelectedDate] = useState(null); // Track the selected date
- const [userProfile, setUserProfile] = useState(null);
-
- const { user } = useUser() || {};
-
- console.log({ user });
-
- useEffect(() => {
- async function setupUserProfile() {
- // check if user exists and logged in
- if (!user) return;
-
- // get user id of logged in user from clerk auth hook
- const userId = user?.id;
-
- if (!userId || typeof userId !== 'string' || !userId?.length) return;
-
- // fetch user by user id from firebase DB
- const [fetchUserError, fetchedUser] = await fetchUserById(userId);
-
- if (fetchUserError) return;
-
- // if user exists, save the data to local userProfile state
- if (fetchedUser !== null) {
- console.log('saved user profile to local state');
- setUserProfile(fetchedUser);
- } else {
- console.log('ready to create new user to DB');
-
- // if user does not exist, create a new user document
- const newUser = {
- userId: user?.id,
- fullName: user?.fullName || '',
- email: user?.emailAddresses[0]?.emailAddress || '',
- profilePicture: user?.imageUrl || '',
- joinedAt: new Date().toISOString(),
- };
- const [saveNewUserError, newUserId] = await saveNewUser(userId, newUser);
-
- if (saveNewUserError) return;
-
- // refetch user id from firebase DB and save the data to local userProfile state
- const [fetchNewUserError, fetchedNewUser] = await fetchUserById(newUserId);
-
- if (fetchNewUserError) return;
-
- // if user exists, save the data to local userProfile state
- if (fetchedNewUser !== null) {
- console.log('saved user profile to local state');
- setUserProfile(fetchedNewUser);
- }
- }
- }
- setupUserProfile();
- }, [user]);
-
- useEffect(() => {
- const checkScreenSize = () => {
- if (window.innerWidth <= 768) {
- setIsMobile(true);
- } else {
- setIsMobile(false);
- }
- };
-
- window.addEventListener('resize', checkScreenSize);
-
- checkScreenSize();
-
- return () => window.removeEventListener('resize', checkScreenSize);
- }, []);
+ const [userSelectedDate, setUserSelectedDate] = useState(null);
const handleAddNewTrade = () => {
- setIsAddNewTradeFormModalOpen(true);
+ setIsAddNewTradeFormSidebarOpen(true);
};
- const handleCloseAddNewTradeFormModal = () => {
- setIsAddNewTradeFormModalOpen(false);
+ const handleCloseAddNewTradeFormSidebar = () => {
+ setIsAddNewTradeFormSidebarOpen(false);
};
const handleUpdateTrade = () => {
- setIsEditTradeFormModalOpen(true);
+ setIsEditTradeFormSidebarOpen(true);
};
- const handleCloseEditTradeFormModal = () => {
- setIsEditTradeFormModalOpen(false);
+ const handleCloseEditTradeFormSidebar = () => {
+ setIsEditTradeFormSidebarOpen(false);
};
const handleCloseTradesSidebar = () => {
setIsTradesSidebarOpen(false);
};
- // Update selected date when user clicks on a date
const handleUserSelectedDate = (date) => {
- setSelectedDate(date);
- setIsTradesSidebarOpen(true); // Open sidebar when a date is selected
+ setUserSelectedDate(date);
+ setIsTradesSidebarOpen(true);
};
- console.log({ userProfile });
+ const handleNewTradeSubmission = (formData) => {
+ const newTrade = {
+ ticker: formData?.ticker || '',
+ tradeType: formData?.tradeType || 'long',
+ tradeDate: (formData?.tradeDate && new Date(formData?.tradeDate).toISOString()) || '',
+ shares: formData?.shares || '',
+ priceOpened: formData?.priceOpened || '',
+ priceClosed: formData?.priceClosed || '',
+ stopLoss: formData?.stopLoss || '',
+ takeProfit: formData?.takeProfit || '',
+ notes: formData?.notes || '',
+ };
+ console.log({ newTrade });
+ };
+
+ if (isLoadingUserDetails) {
+ return (
+
+ Loading your account details...
+
+ );
+ }
+
+ if (userDetailsError) {
+ return (
+
+ {userDetailsError}
+
+ );
+ }
return (
+ {userDetails &&
{JSON.stringify({ userDetails }, null, 2)}}
{isMobile ? (
) : (
)}
@@ -140,30 +101,28 @@ const DashboardPage = () => {
- {isAddNewTradeFormModalOpen && (
- console.log({ formData })}
+ {isAddNewTradeFormSidebarOpen && (
+ handleNewTradeSubmission(formData)}
/>
)}
- {isEditTradeFormModalOpen && (
- console.log({ formData })}
/>
)}
- {/* Sidebar Modal */}
- {isTradesSidebarOpen && selectedDate && (
- //
-
- //
)}
);
diff --git a/src/app/hooks/useIsMobile.js b/src/app/hooks/useIsMobile.js
new file mode 100644
index 0000000..b384b69
--- /dev/null
+++ b/src/app/hooks/useIsMobile.js
@@ -0,0 +1,19 @@
+import { useState, useEffect } from 'react';
+
+export const useIsMobile = (breakpoint = 768) => {
+ const [isMobile, setIsMobile] = useState(false);
+
+ useEffect(() => {
+ const checkScreenSize = () => {
+ setIsMobile(window.innerWidth <= breakpoint);
+ };
+
+ window.addEventListener('resize', checkScreenSize);
+ checkScreenSize();
+
+ return () => {
+ window.removeEventListener('resize', checkScreenSize);
+ };
+ }, [breakpoint]);
+ return isMobile;
+};
diff --git a/src/app/hooks/useUserDetails.js b/src/app/hooks/useUserDetails.js
new file mode 100644
index 0000000..f7b22af
--- /dev/null
+++ b/src/app/hooks/useUserDetails.js
@@ -0,0 +1,72 @@
+import { useState, useEffect } from 'react';
+import { useUser } from '@clerk/nextjs';
+import { fetchUserById, saveNewUser } from '@/utils/users';
+
+export const useUserDetails = () => {
+ const [userDetails, setUserDetails] = useState(null);
+ const [isLoading, setIsLoading] = useState(true);
+ const [error, setError] = useState(null);
+ const { user } = useUser() || {};
+
+ useEffect(() => {
+ const setupUserDetails = async () => {
+ if (!user?.id) {
+ setIsLoading(false);
+ return;
+ }
+
+ try {
+ const userId = user.id;
+ const [fetchUserByIdError, fetchedUser] = await fetchUserById(userId);
+
+ if (fetchUserByIdError) {
+ console.error(fetchUserByIdError);
+ throw new Error(
+ 'Oops! Unable to get user details at the moment. Try refreshing the page OR try again later',
+ );
+ }
+ if (fetchedUser) {
+ setUserDetails(fetchedUser);
+ } else {
+ const newUser = {
+ userId,
+ fullName: user.fullName || '',
+ email: user.emailAddresses?.[0]?.emailAddress || '',
+ profilePicture: user.imageUrl || '',
+ joinedAt: new Date().toISOString(),
+ };
+
+ const [saveNewUserError, newUserId] = await saveNewUser(newUser);
+
+ if (saveNewUserError || !newUserId) {
+ console.error(saveNewUserError);
+ throw new Error(
+ 'Oops! Unable to save new user details at the moment. Try refreshing the page OR try again later',
+ );
+ }
+ const [fetchNewUserError, fetchedNewUser] = await fetchUserById(userId);
+
+ if (fetchNewUserError) {
+ console.error(fetchNewUserError);
+ throw new Error(
+ 'Oops! Unable to get user details at the moment. Try refreshing the page OR try again later',
+ );
+ }
+ setUserDetails(fetchedNewUser);
+ }
+ } catch (error) {
+ setError(error.message);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ setupUserDetails();
+ }, [user]);
+
+ return {
+ userDetails,
+ isLoading,
+ error,
+ };
+};
diff --git a/src/app/settings/page.js b/src/app/settings/page.js
index 95b1801..275557c 100644
--- a/src/app/settings/page.js
+++ b/src/app/settings/page.js
@@ -1,13 +1,24 @@
'use client';
+import { useState, useEffect } from 'react';
+import { useUserDetails } from '../hooks/useUserDetails';
+import Image from 'next/image';
+import DeleteAccountSidebar from '../components/DeleteAccountSidebar';
+import { deleteUserById, updateUserById } from '@/utils/users';
+import PostAccountUpdateSidebar from '../components/PostAccountUpdateSidebar';
import { useUser } from '@clerk/nextjs';
-import { useState } from 'react';
const AccountPage = () => {
- const { user, isLoaded, isSignedIn } = useUser();
+ const { user } = useUser();
- const [profileData, setProfileData] = useState({
- fullName: user?.fullName || '',
+ const {
+ userDetails,
+ isLoading: isLoadingUserDetails,
+ error: userDetailsError,
+ } = useUserDetails();
+
+ const [settingsData, setSettingsData] = useState({
+ fullName: '',
theme: 'light',
dashboardViews: {
monthly: true,
@@ -16,13 +27,46 @@ const AccountPage = () => {
},
});
+ const [isDeleteAccountSidebarOpen, setDeleteAccountSidebarOpen] = useState(false);
+ const [isPostAccountUpdateSidebarOpen, setIsPostAccountUpdateSidebarOpen] = useState(false);
+
+ const [isUpdatingUserDetails, setIsUpdatingUserDetails] = useState(false);
+ const [updatingUserDetailsError, setUpdatingUserDetailsError] = useState(null);
+
+ const [isDeletingUserDetails, setIsDeletingUserDetails] = useState(false);
+
+ useEffect(() => {
+ if (userDetails) {
+ setSettingsData((prev) => ({
+ ...prev,
+ fullName: userDetails?.fullName || '',
+ theme: typeof userDetails?.theme === 'string' ? userDetails?.theme : 'light',
+ dashboardViews: {
+ ...prev.dashboardViews,
+ monthly:
+ typeof userDetails?.dashboardViews?.monthly === 'boolean'
+ ? userDetails?.dashboardViews?.monthly
+ : true,
+ weekly:
+ typeof userDetails?.dashboardViews?.weekly === 'boolean'
+ ? userDetails?.dashboardViews?.weekly
+ : true,
+ daily:
+ typeof userDetails?.dashboardViews?.daily === 'boolean'
+ ? userDetails?.dashboardViews?.daily
+ : true,
+ },
+ }));
+ }
+ }, [userDetails]);
+
const handleProfileChange = (e) => {
const { name, value } = e.target;
- setProfileData((prev) => ({ ...prev, [name]: value }));
+ setSettingsData((prev) => ({ ...prev, [name]: value }));
};
const handleViewToggle = (view) => {
- setProfileData((prev) => ({
+ setSettingsData((prev) => ({
...prev,
dashboardViews: {
...prev.dashboardViews,
@@ -31,17 +75,92 @@ const AccountPage = () => {
}));
};
- if (!isLoaded || !isSignedIn) {
+ const handleDeleteAccount = () => {
+ setIsDeletingUserDetails(true);
+ setTimeout(async () => {
+ await deleteUserById(userDetails?.userId);
+ await user.delete();
+ window.location.href = '/';
+ }, 1500);
+ };
+
+ const openDeleteAccountSidebar = () => {
+ setDeleteAccountSidebarOpen(true);
+ };
+
+ const closeDeleteAccountSidebar = () => {
+ setDeleteAccountSidebarOpen(false);
+ };
+
+ const closeAccountUpdatedSidebar = () => {
+ window.location.href = '/settings';
+ };
+
+ const hasSettingsChanged = () => {
+ if (settingsData.fullName !== userDetails?.fullName) return true;
+ if (settingsData.theme !== userDetails?.theme) return true;
+ if (settingsData.dashboardViews.daily !== userDetails?.dashboardViews?.daily) return true;
+ if (settingsData.dashboardViews.monthly !== userDetails?.dashboardViews?.monthly) return true;
+ if (settingsData.dashboardViews.weekly !== userDetails?.dashboardViews?.weekly) return true;
+ return false;
+ };
+
+ const onSaveChanges = async () => {
+ if (!hasSettingsChanged()) return;
+ setIsUpdatingUserDetails(true);
+
+ try {
+ const updatedUser = {
+ ...userDetails,
+ theme: settingsData.theme,
+ fullName: settingsData.fullName,
+ dashboardViews: {
+ ...(userDetails?.dashboardViews || {}),
+ monthly: settingsData.dashboardViews.monthly,
+ weekly: settingsData.dashboardViews.weekly,
+ daily: settingsData.dashboardViews.daily,
+ },
+ };
+ const [updateUserError, fetchedUpdatedUser] = await updateUserById(
+ userDetails?.userId,
+ updatedUser,
+ );
+
+ if (updateUserError || !fetchedUpdatedUser) {
+ console.error(updateUserError);
+ throw new Error(
+ 'Oops! Unable to save new changes to your account at the moment. Try again later.',
+ );
+ }
+ } catch (error) {
+ setUpdatingUserDetailsError(error.message);
+ } finally {
+ setIsPostAccountUpdateSidebarOpen(true);
+ }
+ };
+
+ const disableSaveChanges = !hasSettingsChanged() || isUpdatingUserDetails;
+
+ if (isLoadingUserDetails) {
return (
-
+
Loading your account details...
-
+
+ );
+ }
+
+ if (userDetailsError) {
+ return (
+
+ {userDetailsError}
;
+
);
}
return (
+ {userDetails &&
{JSON.stringify({ userDetails }, null, 2)}}
{/* Page Header */}
Account Settings
@@ -53,38 +172,70 @@ const AccountPage = () => {
{/* Profile Section */}
Profile Details
-
- {/* Full Name Input */}
-
-
-
+
+ {/* Profile Picture */}
+
+ {userDetails?.profilePicture ? (
+
+ ) : null}
- {/* Theme Preference Dropdown */}
-
-
-
+ {/* Input Fields */}
+
+ {/* Full Name Input */}
+
+
+
+
+
+ {/* Email Field (Disabled) */}
+
+
+
+
+
+ {/* Theme Preference Dropdown */}
+
+
+
+
@@ -93,7 +244,7 @@ const AccountPage = () => {
Dashboard Views
- {Object.entries(profileData.dashboardViews).map(([view, isVisible]) => (
+ {Object.entries(settingsData.dashboardViews).map(([view, isVisible]) => (
{
{/* Save Changes */}
+
+ {/* Account Deletion Section */}
+
+ Delete Account
+
+ Once you delete your account, all your data will be permanently removed and cannot be
+ recovered.
+
+
+
+
+ {/* Delete Confirmation Sidebar */}
+ {isDeleteAccountSidebarOpen && (
+
+ )}
+
+ {/* Account Updated Sidebar */}
+ {isPostAccountUpdateSidebarOpen && (
+
+ )}
);
};
diff --git a/src/services/firebase/addData.js b/src/services/firebase/addData.js
deleted file mode 100644
index 06ac755..0000000
--- a/src/services/firebase/addData.js
+++ /dev/null
@@ -1,26 +0,0 @@
-import { doc, setDoc } from 'firebase/firestore';
-import { FIREBASE_DB } from './config'; // Adjust the path as needed
-
-/**
- * Save a piece of data to a Firestore collection.
- * @param {string} collectionName - The name of the collection.
- * @param {string} documentId - The ID of the document to save or update.
- * @param {object} data - The data to save.
- * @returns {Promise<{ success: boolean, message?: string, error?: string }>}
- * Returns an object with `success: true` if successful, or `success: false` and `error` if not.
- */
-export async function addData(collectionName, documentId, data) {
- try {
- const docRef = doc(FIREBASE_DB, collectionName, documentId);
- await setDoc(docRef, data);
-
- return {
- data: documentId,
- };
- } catch (error) {
- console.error(`Error saving document to "${collectionName}/${documentId}":`, error);
- return {
- error: `Error saving document to "${collectionName}/${documentId}"`,
- };
- }
-}
diff --git a/src/services/firebase/getData.js b/src/services/firebase/getData.js
deleted file mode 100644
index 2f2e54c..0000000
--- a/src/services/firebase/getData.js
+++ /dev/null
@@ -1,32 +0,0 @@
-import { doc, getDoc } from 'firebase/firestore';
-import { FIREBASE_DB } from './config'; // Adjust the path as needed
-
-/**
- * Fetch a document from Firestore.
- * @param {string} collectionName - The name of the collection.
- * @param {string} documentId - The ID of the document to fetch.
- * @returns {Promise<{ success: boolean, data?: object, error?: string }>}
- * Returns an object with `success: true` and `data` if successful, or `success: false` and `error` if not.
- */
-export async function getData(collectionName, documentId) {
- try {
- const docRef = doc(FIREBASE_DB, collectionName, documentId);
- const docSnap = await getDoc(docRef);
-
- if (docSnap.exists()) {
- return {
- data: docSnap.data(),
- };
- } else {
- console.warn(`No document found in collection "${collectionName}" with ID "${documentId}".`);
- return {
- data: null,
- };
- }
- } catch (error) {
- console.error(`Error fetching document from "${collectionName}/${documentId}":`, error);
- return {
- error: `Error fetching document from "${collectionName}/${documentId}`,
- };
- }
-}
diff --git a/src/services/firebase/index.js b/src/services/firebase/index.js
new file mode 100644
index 0000000..cf1cf05
--- /dev/null
+++ b/src/services/firebase/index.js
@@ -0,0 +1,65 @@
+import { deleteDoc, doc, getDoc, setDoc } from 'firebase/firestore';
+import { FIREBASE_DB } from './config';
+
+export async function addData(collectionName, documentId, data) {
+ try {
+ const docRef = doc(FIREBASE_DB, collectionName, documentId);
+ await setDoc(docRef, data);
+ return { data: documentId };
+ } catch (error) {
+ console.error(`Error saving document to "${collectionName}/${documentId}":`, error);
+ return {
+ error: `Error saving document to "${collectionName}/${documentId}"`,
+ };
+ }
+}
+
+export async function getData(collectionName, documentId) {
+ try {
+ const docRef = doc(FIREBASE_DB, collectionName, documentId);
+ const docSnap = await getDoc(docRef);
+ if (docSnap.exists()) {
+ return { data: docSnap.data() };
+ } else {
+ console.warn(`No document found in collection "${collectionName}" with ID "${documentId}".`);
+ return { data: null };
+ }
+ } catch (error) {
+ console.error(`Error fetching document from "${collectionName}/${documentId}":`, error);
+ return {
+ error: `Error fetching document from "${collectionName}/${documentId}`,
+ };
+ }
+}
+
+export async function updateData(collectionName, documentId, updatedData) {
+ try {
+ const docRef = doc(FIREBASE_DB, collectionName, documentId);
+ const docSnap = await getDoc(docRef);
+ const existingData = (docSnap.exists() && docSnap.data()) || {};
+ const mergedData = {
+ ...existingData,
+ ...updatedData,
+ };
+ await setDoc(docRef, mergedData);
+ return { data: mergedData };
+ } catch (error) {
+ console.error(`Error updating document in "${collectionName}/${documentId}":`, error);
+ return {
+ error: `Error updating document in "${collectionName}/${documentId}"`,
+ };
+ }
+}
+
+export async function deleteData(collectionName, documentId) {
+ try {
+ const docRef = doc(FIREBASE_DB, collectionName, documentId);
+ await deleteDoc(docRef);
+ return { data: documentId };
+ } catch (error) {
+ console.error(`Error deleting document from "${collectionName}/${documentId}":`, error);
+ return {
+ error: `Error deleting document from "${collectionName}/${documentId}"`,
+ };
+ }
+}
diff --git a/src/utils/users.js b/src/utils/users.js
index c6c3648..58ab417 100644
--- a/src/utils/users.js
+++ b/src/utils/users.js
@@ -1,12 +1,80 @@
-import { addData } from '@/services/firebase/addData';
-import { getData } from '@/services/firebase/getData';
+import { addData, deleteData, getData, updateData } from '@/services/firebase';
export async function fetchUserById(userId) {
+ if (!userId || typeof userId !== 'string' || !userId.length) {
+ return ['Please provide a valid userId to fetch user', null];
+ }
const { error, data } = await getData('users', userId);
return [error, data];
}
-export async function saveNewUser(userId, newUser) {
+export async function saveNewUser(newUser) {
+ const { userId } = newUser || {};
+ if (!userId || typeof userId !== 'string' || !userId.length) {
+ return ['Please provide a valid `userId` to save new user', null];
+ }
+ if (!isNewUserValid(newUser)) {
+ return ['Please provide a valid `newUser` object', null];
+ }
const { error, data } = await addData('users', userId, newUser);
return [error, data];
}
+
+export async function updateUserById(userId, updatedUser) {
+ if (!userId || typeof userId !== 'string' || !userId.length) {
+ return ['Please provide a valid userId to update user', null];
+ }
+ const { error, data } = await updateData('users', userId, updatedUser);
+ return [error, data];
+}
+
+export async function deleteUserById(userId) {
+ if (!userId || typeof userId !== 'string' || !userId.length) {
+ return ['Please provide a valid userId to delete user', null];
+ }
+ const { error, data } = await deleteData('users', userId);
+ return [error, data];
+}
+
+const isNewUserValid = (newUser) => {
+ if (!newUser) {
+ console.error('Invalid user: `newUser` is null or undefined.');
+ return false;
+ }
+
+ const { userId, email, profilePicture, joinedAt } = newUser;
+
+ const isNonEmptyString = (str) => typeof str === 'string' && str.trim().length > 0;
+ const isValidEmail = (email) => /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
+ const isValidDate = (date) => !isNaN(new Date(date).getTime());
+ const isValidUrl = (url) => {
+ try {
+ new URL(url);
+ return true;
+ } catch {
+ return false;
+ }
+ };
+
+ if (!isNonEmptyString(userId)) {
+ console.error('Invalid `newUser.userId`:', userId);
+ return false;
+ }
+
+ if (!isValidEmail(email)) {
+ console.error('Invalid `newUser.email`:', email);
+ return false;
+ }
+
+ if (profilePicture && !isValidUrl(profilePicture)) {
+ console.error('Invalid `newUser.profilePicture`:', profilePicture);
+ return false;
+ }
+
+ if (!isValidDate(joinedAt)) {
+ console.error('Invalid `newUser.joinedAt`:', joinedAt);
+ return false;
+ }
+
+ return true;
+};