diff --git a/client/.env.example b/client/.env.example
index 63e7f21..16dbde7 100644
--- a/client/.env.example
+++ b/client/.env.example
@@ -1,4 +1,5 @@
REACT_EDITOR=code
APP_ENV=DEVELOPMENT
-NEXT_PUBLIC_BACKEND_URL="http://localhost:8000/api"
\ No newline at end of file
+# NEXT_PUBLIC_BACKEND_URL="http://localhost:8000/api"
+NEXT_PUBLIC_BACKEND_URL="http://127.0.0.1:8000"
\ No newline at end of file
diff --git a/client/package-lock.json b/client/package-lock.json
index 4df6af7..8cc1337 100644
--- a/client/package-lock.json
+++ b/client/package-lock.json
@@ -146,7 +146,6 @@
}
],
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=18"
},
@@ -170,7 +169,6 @@
}
],
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=18"
}
@@ -1383,7 +1381,6 @@
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.80.7.tgz",
"integrity": "sha512-u2F0VK6+anItoEvB3+rfvTO9GEh2vb00Je05OwlUe/A0lkJBgW1HckiY3f9YZa+jx6IOe4dHPh10dyp9aY3iRQ==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@tanstack/query-core": "5.80.7"
},
@@ -1449,7 +1446,6 @@
"integrity": "sha512-AwAfQ2Wa5bCx9WP8nZL2uMZWod7J7/JSplxbTmBQ5ms6QpqNYm672H0Vu9ZVKVngQ+ii4R/byguVEUZQyeg44g==",
"devOptional": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"csstype": "^3.0.2"
}
@@ -1470,7 +1466,6 @@
"integrity": "sha512-4O3idHxhyzjClSMJ0a29AcoK0+YwnEqzI6oz3vlRf3xw0zbzt15MzXwItOlnr5nIth6zlY2RENLsOPvhyrKAQA==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.34.1",
"@typescript-eslint/types": "8.34.1",
@@ -1675,7 +1670,6 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
- "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -2117,7 +2111,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"caniuse-lite": "^1.0.30001718",
"electron-to-chromium": "^1.5.160",
@@ -2863,7 +2856,6 @@
"integrity": "sha512-GsGizj2Y1rCWDu6XoEekL3RLilp0voSePurjZIkxL3wlm5o5EC9VpgaP7lrCvjnkuLvzFBQWB3vWB3K5KQTveQ==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -3069,7 +3061,6 @@
"integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@rtsao/scc": "^1.1.0",
"array-includes": "^3.1.8",
@@ -4568,7 +4559,6 @@
"resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.6.tgz",
"integrity": "sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==",
"license": "MIT",
- "peer": true,
"bin": {
"jiti": "bin/jiti.js"
}
@@ -5493,7 +5483,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"nanoid": "^3.3.11",
"picocolors": "^1.1.1",
@@ -5599,7 +5588,6 @@
"integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==",
"dev": true,
"license": "MIT",
- "peer": true,
"bin": {
"prettier": "bin/prettier.cjs"
},
@@ -5742,7 +5730,6 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
"integrity": "sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -5752,7 +5739,6 @@
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
"integrity": "sha512-Xs1hdnE+DyKgeHJeJznQmYMIBG3TKIHJJT95Q58nHLSrElKlGQqDTR2HQ9fx5CN/Gk6Vh/kupBTDLU11/nDk/g==",
"license": "MIT",
- "peer": true,
"dependencies": {
"scheduler": "^0.26.0"
},
@@ -6630,7 +6616,6 @@
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.17.tgz",
"integrity": "sha512-w33E2aCvSDP0tW9RZuNXadXlkHXqFzSkQew/aIa2i/Sj8fThxwovwlXHSPXTbAHwEIhBFXAedUhP2tueAKP8Og==",
"license": "MIT",
- "peer": true,
"dependencies": {
"@alloc/quick-lru": "^5.2.0",
"arg": "^5.0.2",
@@ -6885,7 +6870,6 @@
"integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==",
"dev": true,
"license": "Apache-2.0",
- "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
diff --git a/client/src/components/ui/navbar_organization.tsx b/client/src/components/ui/navbar_organization.tsx
index 6268f89..41c7adf 100644
--- a/client/src/components/ui/navbar_organization.tsx
+++ b/client/src/components/ui/navbar_organization.tsx
@@ -87,7 +87,10 @@ const Header = () => {
// to prevent the blur event from firing when they are clicked.
onMouseDown={(e) => e.preventDefault()}
>
-
+
+ {/* */}
+
+
)}
diff --git a/client/src/components/ui/popups/user_friends_add_popup.tsx b/client/src/components/ui/popups/user_friends_add_popup.tsx
new file mode 100644
index 0000000..3681420
--- /dev/null
+++ b/client/src/components/ui/popups/user_friends_add_popup.tsx
@@ -0,0 +1,54 @@
+import { removeFriend, sendFriendRequest } from "../../../lib/api/friends";
+import { OtherUsers } from "../user_tables/user_friends_add_table";
+
+const FriendAddItemModal = ({
+ user,
+ onClose,
+}: {
+ user: OtherUsers;
+ onClose: () => void;
+}) => {
+ const handleSend = async () => {
+ await sendFriendRequest(user.id);
+ onClose();
+ };
+
+ const handleRemove = async () => {
+ await removeFriend(user.id);
+ onClose();
+ };
+
+ return (
+
+
+
{user.username}
+
+
+ {!user.is_friend && !user.request_sent && (
+
+ )}
+
+ {user.is_friend && (
+
+ )}
+
+
+
+
+
+ );
+};
+
+export default FriendAddItemModal;
diff --git a/client/src/components/ui/popups/user_friends_recieved_popup.tsx b/client/src/components/ui/popups/user_friends_recieved_popup.tsx
new file mode 100644
index 0000000..2907b94
--- /dev/null
+++ b/client/src/components/ui/popups/user_friends_recieved_popup.tsx
@@ -0,0 +1,101 @@
+// src/components/popups/user_groups_select_popup.tsx
+
+// import Link from "next/link";
+// import React from "react";
+import React, { useEffect, useState } from "react";
+
+import {
+ acceptFriendRequest,
+ declineFriendRequest,
+ getIncomingRequests,
+} from "../../../lib/api/friends";
+
+export type FriendRequest = {
+ id: number;
+ sender: {
+ id: number;
+ username: string;
+ };
+};
+
+type Props = {
+ isOpen: boolean;
+ onClose: () => void;
+};
+
+function UserFriendsRecievedPopup({ isOpen, onClose }: Props) {
+ const [requests, setRequests] = useState([]);
+ const [loading, setLoading] = useState(false);
+ useEffect(() => {
+ if (!isOpen) return;
+
+ setLoading(true);
+ getIncomingRequests()
+ .then((res) => {
+ console.log("Incoming requests:", res.data);
+ setRequests(res.data);
+ })
+
+ .finally(() => setLoading(false));
+ }, [isOpen]);
+
+ if (!isOpen) return null;
+ return (
+
+
+
+
Friend Requests
+
+
+
+ {loading &&
Loading…
}
+
+ {!loading && requests.length === 0 && (
+
No friend requests.
+ )}
+
+
+ {requests.map((r) => (
+
+
{r.sender.username}
+
+
+
+
+
+
+
+ ))}
+
+
+
+ );
+}
+export default UserFriendsRecievedPopup;
diff --git a/client/src/components/ui/popups/user_friends_search_popup.tsx b/client/src/components/ui/popups/user_friends_search_popup.tsx
new file mode 100644
index 0000000..2b53c7a
--- /dev/null
+++ b/client/src/components/ui/popups/user_friends_search_popup.tsx
@@ -0,0 +1,83 @@
+// src/components/popups/user_groups_add_popup.tsx
+
+import Link from "next/link";
+import React from "react";
+
+type ItemModalProps = {
+ // user={selectedItem;
+ isOpen: boolean;
+ onClose: () => void;
+};
+
+function UserFriendsSearchPopup({ isOpen, onClose }: ItemModalProps) {
+ if (!isOpen) return null;
+ return (
+
+
+
+ {/* Header */}
+
+
Add Friends
+
+
+
+ {/* Body */}
+
+ {/* Start */}
+
+ {/* End */}
+ {/*
ID: Ummm ~~
*/}
+
+
+ {/* Footer */}
+
+
+
+
+
+
+
+
+
+ );
+}
+export default UserFriendsSearchPopup;
diff --git a/client/src/components/ui/popups/user_friends_select_popup.tsx b/client/src/components/ui/popups/user_friends_select_popup.tsx
new file mode 100644
index 0000000..219c015
--- /dev/null
+++ b/client/src/components/ui/popups/user_friends_select_popup.tsx
@@ -0,0 +1,136 @@
+// src/components/popups/user_groups_select_popup.tsx
+
+import Link from "next/link";
+import React from "react";
+
+import { removeFriend } from "../../../lib/api/friends";
+import ProfilePicture from "../profile_picture";
+
+type MyFriends = {
+ id: number;
+ // name: string;
+ username: string;
+};
+
+type ItemModalProps = {
+ isOpen: boolean;
+ item: MyFriends | null;
+ onClose: () => void;
+ onFriendRemoved: (id: number) => void;
+};
+
+function UserFriendsSelectPopup({
+ isOpen,
+ item,
+ onClose,
+ onFriendRemoved,
+}: ItemModalProps) {
+ if (!item) return;
+ const handleRemoveFriend = async () => {
+ try {
+ await removeFriend(item.id);
+ onFriendRemoved(item.id);
+ onClose();
+ } catch (err) {
+ console.error("Failed to remove friend", err);
+ }
+ };
+ if (!isOpen || !item) return null;
+ return (
+
+
+
+
+ {/* Header */}
+ {/* Avatar */}
+
+ {/* Implement below when we implement a profile picture field */}
+
+ {/* {profile_picture === Null ? (
+
+ ) : (
+ item.profile_picture
+ )}
+ } */}
+
+ {/* Text column */}
+
+ {/* Name */}
+ {/* Replace below with logic to forward to unique profile of other user */}
+
+
+ {item.username}
+
+
+
+ {/* other */}
+
+
User: {item.id}
+
+
+ {" "}
+ Mutual Groups: {item.id}
+
+
+
+
+
+
+
+ {/* Footer */}
+
+
+
+
+
+
+
+
+
+ );
+}
+export default UserFriendsSelectPopup;
diff --git a/client/src/components/ui/popups/user_friends_sent_popup.tsx b/client/src/components/ui/popups/user_friends_sent_popup.tsx
new file mode 100644
index 0000000..aebb030
--- /dev/null
+++ b/client/src/components/ui/popups/user_friends_sent_popup.tsx
@@ -0,0 +1,80 @@
+// src/components/popups/user_groups_select_popup.tsx
+
+// import Link from "next/link";
+// import React from "react";
+import React, { useEffect, useState } from "react";
+
+import { cancelFriendRequest,getSentRequests } from "../../../lib/api/friends";
+
+export type FriendRequest = {
+ id: number;
+ receiver: {
+ id: number;
+ username: string;
+ };
+};
+
+type Props = {
+ isOpen: boolean;
+ onClose: () => void;
+};
+
+function UserFriendsSentPopup({ isOpen, onClose }: Props) {
+ const [requests, setRequests] = useState([]);
+ const [loading, setLoading] = useState(false);
+ useEffect(() => {
+ if (!isOpen) return;
+
+ setLoading(true);
+ getSentRequests()
+ .then((res) => setRequests(res.data))
+ .finally(() => setLoading(false));
+ }, [isOpen]);
+
+ if (!isOpen) return null;
+ return (
+
+
+
+
Friend Requests
+
+
+
+ {loading &&
Loading…
}
+
+ {!loading && requests.length === 0 && (
+
No friend requests.
+ )}
+
+
+ {requests.map((r) => (
+
+
{r.receiver.username}
+
+
+
+
+
+ ))}
+
+
+
+ );
+}
+export default UserFriendsSentPopup;
diff --git a/client/src/components/ui/popups/user_groups_select_popup.tsx b/client/src/components/ui/popups/user_groups_select_popup.tsx
new file mode 100644
index 0000000..4886c63
--- /dev/null
+++ b/client/src/components/ui/popups/user_groups_select_popup.tsx
@@ -0,0 +1,115 @@
+// src/components/popups/user_groups_select_popup.tsx
+
+import Link from "next/link";
+import React from "react";
+
+type MyGroups = {
+ id: number;
+ name: string;
+};
+
+type ItemModalProps = {
+ isOpen: boolean;
+ item: MyGroups | null;
+ onClose: () => void;
+};
+
+function UserGroupsSelectPopup({ isOpen, item, onClose }: ItemModalProps) {
+ if (!isOpen || !item) return null;
+ return (
+
+
+
+
+ {/* Header */}
+ {/* Avatar */}
+
+ {/* Text column */}
+
+ {/* Name */}
+
+
+ {item.name}
+
+
+ {/* other */}
+
+
Group: {item.id}
+
+
+ {" "}
+ Mutual Friends: {item.id}
+
+
+
+
+
+ View Inventory
+
+
+
+
+
+
+ {/* Footer */}
+
+
+
+
+
+
+
+
+
+ );
+}
+export default UserGroupsSelectPopup;
diff --git a/client/src/components/ui/popups/user_inventory_edit_popup.tsx b/client/src/components/ui/popups/user_inventory_edit_popup.tsx
new file mode 100644
index 0000000..79a6123
--- /dev/null
+++ b/client/src/components/ui/popups/user_inventory_edit_popup.tsx
@@ -0,0 +1,112 @@
+// src/components/popups/user_groups_select_popup.tsx
+
+import Link from "next/link";
+import React from "react";
+
+type InventoryItem = {
+ id: number;
+ name: string;
+ date: string;
+ dateNew: Date;
+};
+
+type ItemModalProps = {
+ isOpen: boolean;
+ item: InventoryItem | null;
+ onClose: () => void;
+};
+
+function UserInventoryEditPopup({ isOpen, item, onClose }: ItemModalProps) {
+ if (!isOpen || !item) return null;
+ return (
+
+
+
+
+ {/* Avatar */}
+
+ {/* Text column */}
+
+ {/* Name */}
+
+ {item.name}
+
+ {/* other */}
+
+
Category: {item.id}
+
+ {" "}
+ Owned by: {item.id}
+
+
+
+
+
+ {/* Body */}
+ {/* Date Change */}
+
+
+ Extend/Reduce Return Date:
+
{" "}
+
+ {/* Description */}
+
+
+
+
+ Max 300 characters
+
+
+
+
+ {/* Footer */}
+
+
+
+
+
+
+
+
+ );
+}
+export default UserInventoryEditPopup;
diff --git a/client/src/components/ui/popups/user_inventory_select_popup.tsx b/client/src/components/ui/popups/user_inventory_select_popup.tsx
new file mode 100644
index 0000000..9704e6d
--- /dev/null
+++ b/client/src/components/ui/popups/user_inventory_select_popup.tsx
@@ -0,0 +1,93 @@
+// src/components/popups/user_groups_select_popup.tsx
+
+// import Link from "next/link";
+import React from "react";
+
+import ProfilePicture from "../profile_picture"; // Replace with item image when available
+
+type InventoryItem = {
+ id: number;
+ name: string;
+ date: string;
+ dateNew: Date;
+};
+
+type ItemModalProps = {
+ isOpen: boolean;
+ item: InventoryItem | null;
+ onClose: () => void;
+ onEdit: (item: InventoryItem) => void;
+};
+
+function UserInventorySelectPopup({
+ isOpen,
+ item,
+ onClose,
+ onEdit,
+}: ItemModalProps) {
+ if (!isOpen || !item) return null;
+ return (
+
+
+
+
+ {/* Header */}
+ {/* Avatar */}
+
+ {/* Text column */}
+
+ {/* Name */}
+
+ {item.name}
+
+ {/* other */}
+
+
Category: {item.id}
+
+ {" "}
+ Owned by: {item.id}
+
+
+
+
+
+
+ {/* Body */}
+
+
+ Description: (description here) {item.id}
+
+
+ Date Due (New):{" "}
+ {item.dateNew.toISOString().split("T")[0]}
+
+
+
+ {/* Footer */}
+
+
+
+
+
+
+ );
+}
+export default UserInventorySelectPopup;
diff --git a/client/src/components/ui/popups/user_owned_organizations_popup.tsx b/client/src/components/ui/popups/user_owned_organizations_popup.tsx
new file mode 100644
index 0000000..ec41833
--- /dev/null
+++ b/client/src/components/ui/popups/user_owned_organizations_popup.tsx
@@ -0,0 +1,86 @@
+// src/components/popups/user_groups_select_popup.tsx
+
+// import Link from "next/link";
+// import React from "react";
+import Link from "next/link";
+import React, { useEffect, useState } from "react";
+
+import { cancelFriendRequest, getSentRequests } from "../../../lib/api/friends";
+
+export type FriendRequest = {
+ id: number;
+ receiver: {
+ id: number;
+ username: string;
+ };
+};
+
+type Props = {
+ isOpen: boolean;
+ onClose: () => void;
+};
+
+function UserOwnedOrganizations({ isOpen, onClose }: Props) {
+ const [requests, setRequests] = useState([]);
+ const [loading, setLoading] = useState(false);
+ useEffect(() => {
+ if (!isOpen) return;
+
+ setLoading(true);
+ getSentRequests()
+ .then((res) => setRequests(res.data))
+ .finally(() => setLoading(false));
+ }, [isOpen]);
+
+ if (!isOpen) return null;
+ return (
+
+
+
+
Friend Requests
+
+
+
+ {loading &&
Loading…
}
+
+ {!loading && requests.length === 0 && (
+
No friend requests.
+ )}
+
+
+ {requests.map((r) => (
+
+
{r.receiver.username}
+
+
+
+
+
+ ))}
+
+
+
+
+
+
+ );
+}
+export default UserOwnedOrganizations;
diff --git a/client/src/components/ui/profile_picture.tsx b/client/src/components/ui/profile_picture.tsx
new file mode 100644
index 0000000..4a1ebcf
--- /dev/null
+++ b/client/src/components/ui/profile_picture.tsx
@@ -0,0 +1,12 @@
+// function ProfilePicture({ }) {
+const ProfilePicture = () => {
+ return (
+
+ );
+};
+export default ProfilePicture;
diff --git a/client/src/components/ui/user_navbar.tsx b/client/src/components/ui/user_navbar.tsx
index bc1deeb..729b849 100644
--- a/client/src/components/ui/user_navbar.tsx
+++ b/client/src/components/ui/user_navbar.tsx
@@ -1,60 +1,174 @@
+// src/pages/user_dashboard.tsx (Update the import path)
+//import "";
+
import Link from "next/link";
+import { useState } from "react";
+
+import { useAuth } from "../../context/AuthContext";
+import UserFriendsRecieved from "../ui/popups/user_friends_recieved_popup";
+import UserFriendsSent from "../ui/popups/user_friends_sent_popup";
+// import UserOwnedOrganizations from "../ui/popups/user_owned_organizations_popup";
+import UserOwnedOrganizations from "../ui/popups/user_owned_organizations_popup";
+
+const UserNavbar = () => {
+ const { user, logout } = useAuth();
+ const [isModalOpen, setIsModalOpen] = useState(false);
+ // const [activeModal, setActiveModal] = useState<"recieved" | "sent" | null>(
+ const [activeModal, setActiveModal] = useState<
+ "recieved" | "sent" | "organizations" | null
+ >(null);
-export default function User_Navbar() {
return (
-