From 1f594f577f58240450f2847a3c8b880fb0ff2420 Mon Sep 17 00:00:00 2001 From: Kush Patel Date: Tue, 6 Jan 2026 17:15:15 +0800 Subject: [PATCH 01/70] user side navbar implementation --- client/src/components/ui/user_navbar.tsx | 88 ++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 client/src/components/ui/user_navbar.tsx diff --git a/client/src/components/ui/user_navbar.tsx b/client/src/components/ui/user_navbar.tsx new file mode 100644 index 0000000..66f2981 --- /dev/null +++ b/client/src/components/ui/user_navbar.tsx @@ -0,0 +1,88 @@ +// src/pages/user_dashboard.tsx (Update the import path) +//import ""; + +import Link from "next/link"; + +const UserNavbar = () => { + return ( + <> + {/* 1. Navigation Bar */} + {/*
*/} +
+ +
+ + ); +}; + +// export to make the function available to other parts of the app +export default UserNavbar; From 0071f80a0efb6aef7517e3947fc218cf60566840 Mon Sep 17 00:00:00 2001 From: Kush Patel Date: Tue, 6 Jan 2026 17:23:18 +0800 Subject: [PATCH 02/70] User dashboard implemented with imported friends inventory and groups tables --- client/src/pages/user_dashboard.tsx | 87 ++++++++++++++++++++++++++--- 1 file changed, 80 insertions(+), 7 deletions(-) diff --git a/client/src/pages/user_dashboard.tsx b/client/src/pages/user_dashboard.tsx index e5a6572..285c360 100644 --- a/client/src/pages/user_dashboard.tsx +++ b/client/src/pages/user_dashboard.tsx @@ -1,6 +1,18 @@ -// src/pages/organization_dashboard.tsx (Update the import path) +// src/pages/user_dashboard.tsx (Update the import path) +//import ""; import Head from "next/head"; +import Link from "next/link"; + +import UserNavbar from "../components/ui/user_navbar"; +import UserFriendsTable from "../components/ui/user_tables/user_friends_table"; +import UserGroupsTable from "../components/ui/user_tables/user_groups_table"; +import UserInventoryTable from "../components/ui/user_tables/user_inventory_table"; + +{ + /* optional: later have a list for friends in said groups or something in above "my groups" */ +} + // The Header component is now one directory level up from 'pages', // so the path is '../components/Header' //import Header from '../components/Header'; @@ -11,15 +23,76 @@ const UserDashboardPage = () => { User Dashboard -
- {/* 1. Navigation Bar */} - {/*
*/} + {/* 1. Navigation Bar */} + + {/* Page background */} +
{/* 2. Main content wrapper */} -
+
{/* Components for Statistics, Recent Activity, and Quick Actions */} -

Welcome to the User Dashboard!

{" "} - {/* Add a temporary header to confirm it works */} +

+ Welcome to the User Dashboard! +

+ + {/* Dashboard Grid */} +
+ {/* Inventory Panel (Scrollable) */} +
+
+

My Inventory

+ {/* route to inventory page TODO:*/} + + + +
+ +
+ + {/* Top right panel */} +
+
+

Due Soon

+ {/* route to inventory page TODO: (or just regular inventory page ig) */} + + + +
+ +
+ + {/* Middle right panel */} +
+
+

My Groups

+ {/* route to groups page TODO:*/} + + + +
+ +
+ + {/* Bottom right panel */} +
+
+

My Friends

+ {/* route to groups page TODO:*/} + + + +
+ +
+
From 4b32a61a0f2d0d30cc98f5c9275a0d85a3af37f0 Mon Sep 17 00:00:00 2001 From: Kush Patel Date: Tue, 6 Jan 2026 17:37:38 +0800 Subject: [PATCH 03/70] User Friends page implementation selecting and managing individual friends --- .../ui/popups/user_friends_select_popup.tsx | 109 ++++++++++++++++++ .../ui/user_tables/user_friends_table.tsx | 58 ++++++++++ client/src/pages/user_friends.tsx | 62 ++++++++++ 3 files changed, 229 insertions(+) create mode 100644 client/src/components/ui/popups/user_friends_select_popup.tsx create mode 100644 client/src/components/ui/user_tables/user_friends_table.tsx create mode 100644 client/src/pages/user_friends.tsx 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..f109af0 --- /dev/null +++ b/client/src/components/ui/popups/user_friends_select_popup.tsx @@ -0,0 +1,109 @@ +// src/components/popups/user_groups_select_popup.tsx + +import Link from "next/link"; +import React from "react"; + +type MyFriends = { + id: number; + name: string; +}; + +type ItemModalProps = { + isOpen: boolean; + item: MyFriends | null; + onClose: () => void; +}; + +function UserFriendsSelectPopup({ isOpen, item, onClose }: ItemModalProps) { + if (!isOpen || !item) return null; + return ( +
+
+ {/* Header */} +
+
+
+
+
+ {/*
*/} + {/* Body */} +
+ +

+ {item.name} +

+ +
+

User: {item.id}

   {" "} + {/* Unique Profile Identifier */} +

+ Mutual Groups: {item.id} +

{" "} + {/* Add link/popup here? */} +
+
+
+ + {/*
*/} +
+ + {/* Footer */} +
+ + + + + +
+
+
+ ); +} +export default UserFriendsSelectPopup; diff --git a/client/src/components/ui/user_tables/user_friends_table.tsx b/client/src/components/ui/user_tables/user_friends_table.tsx new file mode 100644 index 0000000..f26fca0 --- /dev/null +++ b/client/src/components/ui/user_tables/user_friends_table.tsx @@ -0,0 +1,58 @@ +// src/pages/user_dashboard.tsx (Update the import path) +//import ""; +import { useEffect,useState } from "react"; + +import FriendItemModal from "../popups/user_friends_select_popup"; + +export type MyFriends = { + id: number; + name: string; +}; + +const UserFriendsTable = () => { + const [selectedItem, setSelectedItem] = useState(null); + const [isModalOpen, setIsModalOpen] = useState(false); + + // Mock data (replace with Django API call later) + const [friends, SetFriends] = useState([]); + // Mock data (replace with Django API call later) + useEffect(() => { + const mockFriend: MyFriends[] = Array.from({ length: 30 }, (_, i) => ({ + id: i + 1, + name: `Friend ${i + 1}`, + })); + SetFriends(mockFriend); + }, []); + + return ( + <> + {/* Scrollable container */} +
+ {/* logic for clicking to open pop-up window to edit/resolve an item*/} + + {friends.length === 0 ? ( +

No friends found.

+ ) : ( + friends.map((item) => ( +
{ + setSelectedItem(item); + setIsModalOpen(true); + }} + className="flex items-center justify-between border-b px-3 py-3 last:border-b-0 hover:bg-gray-50" + > + {item.name} +
+ )) + )} +
+ setIsModalOpen(false)} + /> + + ); +}; +export default UserFriendsTable; diff --git a/client/src/pages/user_friends.tsx b/client/src/pages/user_friends.tsx new file mode 100644 index 0000000..1e3a9dd --- /dev/null +++ b/client/src/pages/user_friends.tsx @@ -0,0 +1,62 @@ +// src/pages/user_dashboard.tsx (Update the import path) +//import ""; + +import Head from "next/head"; +import { useState } from "react"; + +// import {useState, useEffect} from "react"; +import UserFriendsAddPopup from "../components/ui/popups/user_friends_add_popup"; +import UserNavbar from "../components/ui/user_navbar"; +import UserFriendsTable from "../components/ui/user_tables/user_friends_table"; + +const UserFriendsPage = () => { + const [isModalOpen, setIsModalOpen] = useState(false); + return ( + <> + + My Friends + + {/* Navbar */} + + {/* Page background */} +
+ {/* 2. Main content wrapper */} +
+ {/* Components for Statistics, Recent Activity, and Quick Actions */} +

My Friends

+ + {/* Inventory Grid */} +
+
+
+

My Friends

+ + +
+ {/* + + */} + +
+
+
+ setIsModalOpen(false)} + /> +
+ + ); +}; + +// export to make the function available to other parts of the app +export default UserFriendsPage; From 36245b0d49110c0d9d8fa67f55bdb2fc5a523850 Mon Sep 17 00:00:00 2001 From: Kush Patel Date: Tue, 6 Jan 2026 17:48:23 +0800 Subject: [PATCH 04/70] Popup implementation on user friends page for quick user search --- .../ui/popups/user_friends_add_popup.tsx | 95 +++++++++++++++++++ 1 file changed, 95 insertions(+) create mode 100644 client/src/components/ui/popups/user_friends_add_popup.tsx 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..306a789 --- /dev/null +++ b/client/src/components/ui/popups/user_friends_add_popup.tsx @@ -0,0 +1,95 @@ +// src/components/popups/user_groups_add_popup.tsx + +import Link from "next/link"; +import React from "react"; + +// type MyFriends = { +// id: number; +// name: string; +// } + +type ItemModalProps = { + isOpen: boolean; + onClose: () => void; +}; + +function UserFriendsAddPopup({ isOpen, onClose }: ItemModalProps) { + if (!isOpen) return null; + return ( +
+
+ {/* Header */} +
+

Add Friends

+ +
+ + {/* Body */} +
+ {/* Start */} +
+
+ + +
+
+ {/* End */} + {/*

ID: Ummm ~~

*/} +
+ + {/* Footer */} +
+ + + + + {/* */} + + +
+
+
+ ); +} +export default UserFriendsAddPopup; From 507673b9ae227e7c74ca4fbb6288dc6338f8f797 Mon Sep 17 00:00:00 2001 From: Kush Patel Date: Tue, 6 Jan 2026 17:50:10 +0800 Subject: [PATCH 05/70] User groups page implementation with imported user groups table --- .../ui/user_tables/user_groups_table.tsx | 58 +++++++++++++++++++ client/src/pages/user_groups.tsx | 51 ++++++++++++++++ 2 files changed, 109 insertions(+) create mode 100644 client/src/components/ui/user_tables/user_groups_table.tsx create mode 100644 client/src/pages/user_groups.tsx diff --git a/client/src/components/ui/user_tables/user_groups_table.tsx b/client/src/components/ui/user_tables/user_groups_table.tsx new file mode 100644 index 0000000..7e1befd --- /dev/null +++ b/client/src/components/ui/user_tables/user_groups_table.tsx @@ -0,0 +1,58 @@ +// src/pages/user_dashboard.tsx (Update the import path) +//import ""; +import { useEffect,useState } from "react"; + +import GroupsItemModal from "../popups/user_groups_select_popup"; + +export type MyGroups = { + id: number; + name: string; +}; + +const UserGroupsTable = () => { + const [selectedItem, setSelectedItem] = useState(null); + const [isModalOpen, setIsModalOpen] = useState(false); + + // Mock data (replace with Django API call later) + const [groups, SetGroups] = useState([]); + // groups mock data + useEffect(() => { + const mockGroups: MyGroups[] = Array.from({ length: 30 }, (_, i) => ({ + id: i + 1, + name: `Group ${i + 1}`, + })); + SetGroups(mockGroups); + }, []); + + return ( + <> + {/* Scrollable container */} +
+ {/* logic for clicking to open pop-up window to edit/resolve an item*/} + + {groups.length === 0 ? ( +

No groups found.

+ ) : ( + groups.map((item) => ( +
{ + setSelectedItem(item); + setIsModalOpen(true); + }} + className="flex items-center justify-between border-b px-3 py-3 last:border-b-0 hover:bg-gray-50" + > + {item.name} +
+ )) + )} +
+ setIsModalOpen(false)} + /> + + ); +}; +export default UserGroupsTable; diff --git a/client/src/pages/user_groups.tsx b/client/src/pages/user_groups.tsx new file mode 100644 index 0000000..e243e49 --- /dev/null +++ b/client/src/pages/user_groups.tsx @@ -0,0 +1,51 @@ +// src/pages/user_dashboard.tsx (Update the import path) +//import ""; + +import Head from "next/head"; + +import UserNavbar from "../components/ui/user_navbar"; +// import {useState, useEffect} from "react"; +import UserGroupsTable from "../components/ui/user_tables/user_groups_table"; + +// type MyGroups = { +// id: number; +// name: string; +// } + +{ + /* optional: later have a list for friends in said groups or something in above "my groups" */ +} + +// The Header component is now one directory level up from 'pages', +// so the path is '../components/Header' +//import Header from '../components/Header'; + +const UserGroupsPage = () => { + return ( + <> + + My Groups + + {/* Page background */} + +
+ {/* 2. Main content wrapper */} +
+ {/* Components for Statistics, Recent Activity, and Quick Actions */} +

My Groups

+ + {/* Inventory Grid */} +
+
+

My Groups

+ +
+
+
+
+ + ); +}; + +// export to make the function available to other parts of the app +export default UserGroupsPage; From a029d7cd91ed9e4196d4832603c609e0f54f2283 Mon Sep 17 00:00:00 2001 From: Kush Patel Date: Tue, 6 Jan 2026 17:50:43 +0800 Subject: [PATCH 06/70] User inventory page implementation with imported user inventory table --- .../ui/user_tables/user_inventory_table.tsx | 74 +++++++++++++++++++ client/src/pages/user_inventory.tsx | 38 ++++++++++ 2 files changed, 112 insertions(+) create mode 100644 client/src/components/ui/user_tables/user_inventory_table.tsx create mode 100644 client/src/pages/user_inventory.tsx diff --git a/client/src/components/ui/user_tables/user_inventory_table.tsx b/client/src/components/ui/user_tables/user_inventory_table.tsx new file mode 100644 index 0000000..4f54084 --- /dev/null +++ b/client/src/components/ui/user_tables/user_inventory_table.tsx @@ -0,0 +1,74 @@ +// src/pages/user_dashboard.tsx (Update the import path) +//import ""; +import { useEffect,useState } from "react"; + +import InventoryItemModal from "../popups/user_inventory_select_popup"; + +export type InventoryItem = { + id: number; + name: string; + date: string; +}; + +const UserInventoryTable = () => { + const [inventory, setInventory] = useState([]); + const [selectedItem, setSelectedItem] = useState(null); + const [isModalOpen, setIsModalOpen] = useState(false); + + // Mock data (replace with Django API call later) + useEffect(() => { + const mockInventory: InventoryItem[] = Array.from( + { length: 30 }, + (_, i) => ({ + id: i + 1, + name: `Item ${i + 1}`, + date: + ((Math.floor(Math.random() * 10) % 11) + 1).toString() + + "/" + + (Math.floor(Math.random() * 10) + 1).toString() + + "/2026", + }), + ); + { + /* have + is_due_soon = date is larger than 2 weeks + in the array? -- as flag for due soon inventory */ + } + setInventory(mockInventory); + }, []); + + return ( + <> + {/* Scrollable container */} +
+ {/* logic for clicking to open pop-up window to edit/resolve an item*/} + + {inventory.length === 0 ? ( +

No items found.

+ ) : ( + inventory.map((item) => ( +
{ + setSelectedItem(item); + setIsModalOpen(true); + }} + className="flex items-center justify-between border-b px-3 py-3 last:border-b-0 hover:bg-gray-50" + > + {item.name} + + date:{item.date} + +
+ )) + )} +
+ setIsModalOpen(false)} + /> + + ); +}; +export default UserInventoryTable; diff --git a/client/src/pages/user_inventory.tsx b/client/src/pages/user_inventory.tsx new file mode 100644 index 0000000..dd16b8e --- /dev/null +++ b/client/src/pages/user_inventory.tsx @@ -0,0 +1,38 @@ +// src/pages/user_dashboard.tsx (Update the import path) +//import ""; + +import Head from "next/head"; + +import UserNavbar from "../components/ui/user_navbar"; +import UserInventoryTable from "../components/ui/user_tables/user_inventory_table"; + +const UserInventoryPage = () => { + return ( + <> + + My Inventory + + {/* Navbar */} + + {/* Page background */} +
+ {/* 2. Main content wrapper */} +
+ {/* Components for Statistics, Recent Activity, and Quick Actions */} +

My Inventory

+ + {/* Inventory Grid */} +
+
+

My Inventory

+ +
+
+
+
+ + ); +}; + +// export to make the function available to other parts of the app +export default UserInventoryPage; From fe701dc97b40a3cc2b84267b29e9c53473832a39 Mon Sep 17 00:00:00 2001 From: Kush Patel Date: Tue, 6 Jan 2026 17:52:29 +0800 Subject: [PATCH 07/70] Popup implementation for selected user group selected on user group/dashboard page --- .../ui/popups/user_groups_select_popup.tsx | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 client/src/components/ui/popups/user_groups_select_popup.tsx 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..b3cd1b3 --- /dev/null +++ b/client/src/components/ui/popups/user_groups_select_popup.tsx @@ -0,0 +1,117 @@ +// 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 */} +
+
+
+
+
+ {/*
*/} + {/* Body */} +
+ +

+ {item.name} +

+ +
+

Group: {item.id}

   {" "} + {/* Unique Profile Identifier */} +

+ Mutual Friends: {item.id} +

{" "} + {/* Add link/popup here? */} +
+ +

+ View Inventory +

+ +
+
+ + {/*
*/} +
+ + {/* Footer */} +
+ + + + + +
+
+
+ ); +} +export default UserGroupsSelectPopup; From 63f9f2cd84f14b7a934bd15adfa98502a3d2b603 Mon Sep 17 00:00:00 2001 From: Kush Patel Date: Tue, 6 Jan 2026 17:52:55 +0800 Subject: [PATCH 08/70] Popup implementation for selected user inventory item selected on user inventory/dashboard page --- .../ui/popups/user_inventory_select_popup.tsx | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 client/src/components/ui/popups/user_inventory_select_popup.tsx 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..e0780bd --- /dev/null +++ b/client/src/components/ui/popups/user_inventory_select_popup.tsx @@ -0,0 +1,86 @@ +// 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; +}; + +type ItemModalProps = { + isOpen: boolean; + item: InventoryItem | null; + onClose: () => void; +}; + +function UserInventorySelectPopup({ isOpen, item, onClose }: ItemModalProps) { + if (!isOpen || !item) return null; + return ( +
+
+ {/* Header */} +
+
+
+
+
+ {/* Body */} +
+ +

+ {item.name} +

+ +
+

Item ID: {item.id}

   {" "} + {/* Unique Profile Identifier */} +

+ Owned By: {item.id} +

{" "} + {/* Add link/popup here? */} +
+
+
+ + {/*
*/} +
+ + {/* Body */} +
+

+ Description: (description here) {item.id} +

+

+ Date Due: {item.date} +

+
+ + {/* Footer */} +
+ + + + +
+
+
+ ); +} +export default UserInventorySelectPopup; From 0649942b1bd8427b3c59bdd5a283c42881a3c25f Mon Sep 17 00:00:00 2001 From: Kush Patel Date: Tue, 6 Jan 2026 18:02:15 +0800 Subject: [PATCH 09/70] Backend Friends implementation --- server/friend/__init__.py | 0 server/friend/admin.py | 25 +++++++++++++++++++++++++ server/friend/apps.py | 6 ++++++ server/friend/migrations/__init__.py | 0 server/friend/tests.py | 3 +++ server/friend/views.py | 3 +++ 6 files changed, 37 insertions(+) create mode 100644 server/friend/__init__.py create mode 100644 server/friend/admin.py create mode 100644 server/friend/apps.py create mode 100644 server/friend/migrations/__init__.py create mode 100644 server/friend/tests.py create mode 100644 server/friend/views.py diff --git a/server/friend/__init__.py b/server/friend/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/server/friend/admin.py b/server/friend/admin.py new file mode 100644 index 0000000..35370fc --- /dev/null +++ b/server/friend/admin.py @@ -0,0 +1,25 @@ +from django.contrib import admin + +from friend.models import FriendRequest, FriendList +# Register your models here. + +class FriendListAdmin(admin.ModelAdmin): + list_filter = ['user'] + list_display = ['user'] + search_fields = ['user'] + readonly_fields = ['user'] + + class Meta: + model = FriendList + +admin.site.register(FriendList, FriendListAdmin) + +class FriendRequestAdmin(admin.ModelAdmin): + list_filter = ['sender', 'receiver'] + list_display = ['sender', 'receiver'] + search_fields = ['sender__username', 'receiver__username'] + readonly_fields = ['sender', 'receiver'] + + class Meta: + model = FriendRequest +admin.site.register(FriendRequest, FriendRequestAdmin) \ No newline at end of file diff --git a/server/friend/apps.py b/server/friend/apps.py new file mode 100644 index 0000000..30fd08a --- /dev/null +++ b/server/friend/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class FriendConfig(AppConfig): + default_auto_field = 'django.db.models.BigAutoField' + name = 'friend' diff --git a/server/friend/migrations/__init__.py b/server/friend/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/server/friend/tests.py b/server/friend/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/server/friend/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/server/friend/views.py b/server/friend/views.py new file mode 100644 index 0000000..91ea44a --- /dev/null +++ b/server/friend/views.py @@ -0,0 +1,3 @@ +from django.shortcuts import render + +# Create your views here. From 7fd1ff5bd6fc824f04bb619fa199c028ca0a8507 Mon Sep 17 00:00:00 2001 From: Kush Patel Date: Tue, 6 Jan 2026 18:07:16 +0800 Subject: [PATCH 10/70] Installation of friend app --- server/server/settings.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/server/server/settings.py b/server/server/settings.py index 1da957f..d16c0d7 100644 --- a/server/server/settings.py +++ b/server/server/settings.py @@ -48,7 +48,8 @@ "rest_framework", "corsheaders", "healthcheck", - "user_profile" + "user_profile", + "friend" ] MIDDLEWARE = [ From c7d34f8656fed50fdc887c39213690af8b5f6db4 Mon Sep 17 00:00:00 2001 From: Kush Patel Date: Tue, 6 Jan 2026 18:07:24 +0800 Subject: [PATCH 11/70] friend update and relationship implementation; friend management - add, remove, unfriend, mutual friend check + friend request - accept, decline, cancel --- server/friend/models.py | 96 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 server/friend/models.py diff --git a/server/friend/models.py b/server/friend/models.py new file mode 100644 index 0000000..ef8048c --- /dev/null +++ b/server/friend/models.py @@ -0,0 +1,96 @@ +from django.db import models +from django.conf import settings +from django.utils import timezone +# Create your models here. + +class FriendList(models.Model): + + user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name="user") + friends = models.ManyToManyField(settings.AUTH_USER_MODEL, blank=True, related_name="friends") + + def __str__(self): + return self.user.username + def add_friend(self, account): + """ + Add a new friend + """ + if not account in self.friends.all(): + self.friends.add(account) + self.save() + + def remove_friend(self, account): + """ + Remove a friend + """ + if account in self.friends.all(): + self.friends.remove(account) + + def unfriend(self, removee): + """ + Initiate action of infriending someone + """ + remover_friends_list = self # person terminating friendship + + # Remove friend from remover friend list + remover_friends_list.remove_friend(removee) + + # Remove friend from removee friend list + friends_list = FriendList.objects.get(user=removee) + friends_list.remove_friend(self.user) + # if we want a mutual removal of friendship, i guess add that below + + def is_mutual_friend(self, friend): + """ + Is this a friend + """ + if friend in self.friends.all(): + return True + return False + +class FriendRequest(models.Model): + """ + Friend consists of two parts, + 1. Sender -- Person sending friend request + 2. Reciever -- Person recieving friend request + """ + sender = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name = "sender") + receiver = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE, related_name = "receiver") + + is_active = models.BooleanField(blank=True, null=False, default=True) + + timestamp = models.DateTimeField(auto_now_add=True) + def __str__(self): + return self.sender.username + + def accept(self): + """ + Accept a friend request + Update both Sender and Receiver friend lists + """ + receiver_friend_list = FriendList.objects.get(user=self.receiver) + if receiver_friend_list: + receiver_friend_list.add_friend(self.sender) + sender_friend_list = FriendList.objects.get(user=self.sender) + if sender_friend_list: + sender_friend_list.add_friend(self.receiver) + self.is_active = False + self.save() + + def decline(self): + """ + Decline a friend request. + It is "declined" by setting the 'is_active' field to False + """ + self.is_active = False + self.save() + + def cancel(self): + """ + Cancel a friend request + It is 'cancelled' by setting the 'is_active' field to False. + Only different from 'decline' with respect to "declining" through generated notification. + """ + self.is_active = False + self.save() + + From f0725facf3cb40a3f78eb42765b60f09a150bae5 Mon Sep 17 00:00:00 2001 From: Kush Patel Date: Tue, 6 Jan 2026 18:07:35 +0800 Subject: [PATCH 12/70] migrations --- server/friend/migrations/0001_initial.py | 35 +++++++++++++++++++ ...002_alter_profile_age_alter_profile_bio.py | 23 ++++++++++++ 2 files changed, 58 insertions(+) create mode 100644 server/friend/migrations/0001_initial.py create mode 100644 server/user_profile/migrations/0002_alter_profile_age_alter_profile_bio.py diff --git a/server/friend/migrations/0001_initial.py b/server/friend/migrations/0001_initial.py new file mode 100644 index 0000000..df541f7 --- /dev/null +++ b/server/friend/migrations/0001_initial.py @@ -0,0 +1,35 @@ +# Generated by Django 5.2.8 on 2025-12-19 08:20 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='FriendList', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('friends', models.ManyToManyField(blank=True, related_name='friends', to=settings.AUTH_USER_MODEL)), + ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='user', to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='FriendRequest', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('is_active', models.BooleanField(blank=True, default=True)), + ('timestamp', models.DateTimeField(auto_now_add=True)), + ('receiver', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='receiver', to=settings.AUTH_USER_MODEL)), + ('sender', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='sender', to=settings.AUTH_USER_MODEL)), + ], + ), + ] diff --git a/server/user_profile/migrations/0002_alter_profile_age_alter_profile_bio.py b/server/user_profile/migrations/0002_alter_profile_age_alter_profile_bio.py new file mode 100644 index 0000000..7ea5d8c --- /dev/null +++ b/server/user_profile/migrations/0002_alter_profile_age_alter_profile_bio.py @@ -0,0 +1,23 @@ +# Generated by Django 5.2.8 on 2025-12-19 08:20 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('user_profile', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='profile', + name='age', + field=models.PositiveIntegerField(blank=True, null=True), + ), + migrations.AlterField( + model_name='profile', + name='bio', + field=models.TextField(blank=True), + ), + ] From 0303b0956d1edc6f606c2e2cb1e4942bdd014cec Mon Sep 17 00:00:00 2001 From: Kush Patel Date: Wed, 7 Jan 2026 13:37:35 +0800 Subject: [PATCH 13/70] Popup implementation for editing user inventory item (base framework) --- .../ui/popups/user_inventory_edit_popup.tsx | 86 +++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 client/src/components/ui/popups/user_inventory_edit_popup.tsx 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..d8a5b0b --- /dev/null +++ b/client/src/components/ui/popups/user_inventory_edit_popup.tsx @@ -0,0 +1,86 @@ +// 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; +}; + +type ItemModalProps = { + isOpen: boolean; + item: InventoryItem | null; + onClose: () => void; +}; + +function UserInventoryEditPopup({ isOpen, item, onClose }: ItemModalProps) { + if (!isOpen || !item) return null; + return ( +
+
+ {/* Header */} +
+
+
+
+
+ {/* Body */} +
+ +

+ {item.name} LOL +

+ +
+

Edit: {item.id}

   {" "} + {/* Unique Profile Identifier */} +

+ Owned By: {item.id} +

{" "} + {/* Add link/popup here? */} +
+
+
+ + {/*
*/} +
+ + {/* Body */} +
+

+ Description: (description here) {item.id} +

+

+ Date Due: {item.date} +

+
+ + {/* Footer */} +
+ + + + +
+
+ + ); +} +export default UserInventoryEditPopup; From 0ff88109df2759c2f17a2dfe21818a6375420ea7 Mon Sep 17 00:00:00 2001 From: Kush Patel Date: Wed, 7 Jan 2026 13:40:56 +0800 Subject: [PATCH 14/70] Updated logic for user inventory to close selection modal and open item edit modal --- .../ui/popups/user_inventory_select_popup.tsx | 27 ++++++++++++++----- .../ui/user_tables/user_inventory_table.tsx | 14 +++++++++- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/client/src/components/ui/popups/user_inventory_select_popup.tsx b/client/src/components/ui/popups/user_inventory_select_popup.tsx index e0780bd..f07ecfc 100644 --- a/client/src/components/ui/popups/user_inventory_select_popup.tsx +++ b/client/src/components/ui/popups/user_inventory_select_popup.tsx @@ -13,15 +13,19 @@ type ItemModalProps = { isOpen: boolean; item: InventoryItem | null; onClose: () => void; + onEdit: (item: InventoryItem) => void; }; -function UserInventorySelectPopup({ isOpen, item, onClose }: ItemModalProps) { +function UserInventorySelectPopup({ + isOpen, + item, + onClose, + onEdit, +}: ItemModalProps) { if (!isOpen || !item) return null; return ( -
+
+ {/* onClick={onClose} */}
{/* Header */}
@@ -67,11 +71,20 @@ function UserInventorySelectPopup({ isOpen, item, onClose }: ItemModalProps) { {/* Footer */}
- + {/* - + */} + - {/*
*/}
- {/* Body */}

- Description: (description here) {item.id} -

-

- Date Due: {item.date} -

+ Extend/Reduce Return Date: +

{" "} + {/* Extend Date Due: {item.date} */} +
+ {/* Description */} + + {/* Body */} {/* Footer */}
-
{/* Body */} + {/* Date Change */}

Extend/Reduce Return Date:

{" "} - {/* Extend Date Due: {item.date} */} + {/* Description */} +
+ +