From 032c12865b2f0d45378e92c7e0732dd87392f1fa Mon Sep 17 00:00:00 2001
From: Mary Caserio <114102750+marycaserio@users.noreply.github.com>
Date: Fri, 6 Dec 2024 11:53:17 -0600
Subject: [PATCH] refactor: donot events alerts (#28)
Please include a summary of the changes and the related issue.
## Type of change
- [x] Bug fix
- [x] New feature
- [ ] Code refactor
- [ ] Breaking change
- [ ] Style update
- [ ] Documentation update
## Checklist
- [x] I have performed a self-review of my code
- [ ] Single file within 200 lines of codes
- [ ] Comments has added
- [x] My changes generate no new warnings
---------
Co-authored-by: Elara Liu <40444637+ZL-Asica@users.noreply.github.com>
---
src/components/Alerts/DonorAlerts.tsx | 158 ++++++++++++------
src/components/Home/DonorDashboard/index.tsx | 35 +---
.../NeedList/NeedDialog.tsx | 1 +
src/components/Schedule/EventsCalendar.tsx | 7 +-
src/components/common/ProtectedRoute.tsx | 35 +++-
src/components/common/SearchBar.tsx | 80 ++++++---
src/hooks/useNeeds.ts | 19 ++-
src/stores/userStore.ts | 2 +
src/types/user.d.ts | 1 +
src/utils/eventsUtils.ts | 1 +
10 files changed, 232 insertions(+), 107 deletions(-)
diff --git a/src/components/Alerts/DonorAlerts.tsx b/src/components/Alerts/DonorAlerts.tsx
index 8b49dce..408943b 100644
--- a/src/components/Alerts/DonorAlerts.tsx
+++ b/src/components/Alerts/DonorAlerts.tsx
@@ -1,70 +1,124 @@
import {
Box,
- Paper,
Typography,
List,
ListItem,
- ListItemText,
+ Card,
+ CardHeader,
+ Button,
} from '@mui/material';
import { lighten, useTheme } from '@mui/material/styles';
+import { useNavigate } from 'react-router-dom';
-// interface DonorAlertsProps {}
+import { useOrganizationStore } from '@/stores';
+import { useSavedOrgs } from '@/hooks';
const DonorAlerts = () => {
- const alerts = [
- {
- id: 1,
- title: 'Food Shortage Alert',
- message: 'Urgent need for canned food in Downtown area.',
- category: 'Food',
- },
- {
- id: 2,
- title: 'Volunteers Needed',
- message: 'Animal Shelter needs volunteers this weekend.',
- category: 'Volunteer',
- },
- {
- id: 3,
- title: 'Funding Goal Reached',
- message: 'Youth Theater Company has reached its funding goal!',
- category: 'Funding',
- },
- ];
-
const theme = useTheme();
+ const navigate = useNavigate();
+
+ const { savedOrgs } = useSavedOrgs();
+ const savedOrgUids = new Set(savedOrgs.map((org) => org.uid));
+
+ const organizationProfiles = useOrganizationStore(
+ (state) => state.organizationProfiles
+ );
+
+ // Filtered organizations based on savedOrgUids
+ const filteredOrganizations = organizationProfiles.filter((org) =>
+ savedOrgUids.has(org.uid)
+ );
+
+ // Get recently added needs
+ const recentNeeds = [];
+ for (const org of filteredOrganizations) {
+ if (org.needs) {
+ for (const need of org.needs) {
+ const createdAt = new Date(need.createdAt);
+ const now = new Date();
+ const timeDiffMs = now.getTime() - createdAt.getTime();
+ const hoursAgo = Math.floor(timeDiffMs / (1000 * 60 * 60));
+ const name = org.name;
+
+ if (hoursAgo <= 48) {
+ recentNeeds.push({
+ need,
+ hoursAgo,
+ name,
+ });
+ }
+ }
+ }
+ }
+
+ const handleViewItem = (orgName: string) => {
+ navigate(`/?search=${encodeURIComponent(orgName)}`);
+ };
return (
-
- Alerts
-
+
+ Alerts
+
+
- {alerts.length > 0 ? (
-
- {alerts.map((alert) => (
-
-
+ {recentNeeds.length > 0 ? (
+
+ {recentNeeds.map((needData, index) => (
+
+
+
+ {needData.need.itemName}
+
+ }
+ subheader={
+
+ Added {needData.hoursAgo} hours ago by {needData.name}
+
+ }
+ action={
+
+ }
/>
-
- ))}
-
- ) : (
-
- No alerts match your search.
-
- )}
-
+
+
+ ))}
+
+ ) : (
+
+ No recent needs found.
+
+ )}
);
};
diff --git a/src/components/Home/DonorDashboard/index.tsx b/src/components/Home/DonorDashboard/index.tsx
index 658f048..3e2985a 100644
--- a/src/components/Home/DonorDashboard/index.tsx
+++ b/src/components/Home/DonorDashboard/index.tsx
@@ -1,40 +1,22 @@
import { Box, Typography } from '@mui/material';
import { filter, lowerCase, some } from 'es-toolkit/compat';
-import { useEffect, useState } from 'react';
-import { toast } from 'sonner';
+import { useState } from 'react';
import OrganizationCard from './OrganizationCard';
import { useOrganizationStore } from '@/stores';
-import { SearchBar, LoadingCircle } from '@/components/common';
+import SearchBar from '@/components/common/SearchBar';
const DonorDashboard = () => {
const [searchQuery, setSearchQuery] = useState('');
- const {
- organizationProfiles,
- fetchProfiles,
- subscribeToProfiles,
- loading,
- error,
- } = useOrganizationStore();
-
- useEffect(() => {
- fetchProfiles();
- const unsubscribe = subscribeToProfiles;
-
- return () => unsubscribe && unsubscribe();
- }, [fetchProfiles, subscribeToProfiles]);
-
- if (loading) return ;
- if (error) {
- toast.error(error);
- return
{error}
;
- }
+ const organizationProfiles = useOrganizationStore(
+ (state) => state.organizationProfiles
+ );
// Filtered organizations based on search query
const filteredOrganizations = filter(organizationProfiles, (org) => {
- if (org.name === '' || !org.name) return false;
+ if (!org.name) return false;
const searchTerm = lowerCase(searchQuery);
return (
lowerCase(org.name).includes(searchTerm) ||
@@ -45,10 +27,7 @@ const DonorDashboard = () => {
return organizationProfiles.length > 0 ? (
-
+
{filteredOrganizations.map((org) => (
{
key={day.toString()}
overlap='circular'
badgeContent={isSelected ? '•' : undefined}
- color='primary'
+ sx={{
+ '& .MuiBadge-badge': {
+ fontSize: '30px',
+ color: (theme) => theme.palette.primary.main,
+ },
+ }}
>
{
);
}
+ if (user.role !== 'organization') {
+ const orgLoading = useOrganizationStore((state) => state.loading);
+ const subscribeToProfiles = useOrganizationStore(
+ (state) => state.subscribeToProfiles
+ );
+ const error = useOrganizationStore((state) => state.error);
+
+ useEffect(() => {
+ const unsubscribe = subscribeToProfiles;
+
+ return () => unsubscribe && unsubscribe();
+ }, [subscribeToProfiles]);
+
+ if (orgLoading) return ;
+
+ if (error) {
+ toast.error(error);
+ return (
+
+ {error}
+
+ );
+ }
+ }
+
return element;
};
diff --git a/src/components/common/SearchBar.tsx b/src/components/common/SearchBar.tsx
index db76a90..1f07e2d 100644
--- a/src/components/common/SearchBar.tsx
+++ b/src/components/common/SearchBar.tsx
@@ -1,30 +1,64 @@
+import type { ChangeEvent } from 'react';
+import { useCallback, useMemo, useEffect, useState } from 'react';
import { TextField, Box, Button } from '@mui/material';
+import { useSearchParams } from 'react-router-dom';
+import { debounce } from 'es-toolkit/compat';
+
+const sanitizeQuery = (query: string): string => {
+ return query.replace(/[^\s\w]/gi, '').trim();
+};
interface SearchBarProps {
- searchQuery: string;
- setSearchQuery: (query: string) => void;
+ onSearchChange: (query: string) => void;
}
-const SearchBar: React.FC = ({
- searchQuery,
- setSearchQuery,
-}) => (
-
- setSearchQuery(e.target.value)}
- />
-
-
-);
+const SearchBar = ({ onSearchChange }: SearchBarProps) => {
+ const [searchParams, setSearchParams] = useSearchParams();
+ const [query, setQuery] = useState(searchParams.get('search') || '');
+
+ const debouncedUpdate = useMemo(
+ () =>
+ debounce((value: string) => {
+ setSearchParams({ search: value || '' }, { replace: true });
+ onSearchChange(value);
+ }, 300),
+ [setSearchParams, onSearchChange]
+ );
+
+ const handleInputChange = useCallback(
+ (e: ChangeEvent) => {
+ const sanitizedQuery = sanitizeQuery(e.target.value);
+ setQuery(sanitizedQuery);
+ debouncedUpdate(sanitizedQuery);
+ },
+ [debouncedUpdate]
+ );
+
+ // Initialize the search query from URL on mount
+ useEffect(() => {
+ const initialQuery = searchParams.get('search') || '';
+ setQuery(initialQuery);
+ onSearchChange(initialQuery);
+ }, [searchParams, onSearchChange]);
+
+ return (
+
+
+
+
+ );
+};
export default SearchBar;
diff --git a/src/hooks/useNeeds.ts b/src/hooks/useNeeds.ts
index 5a5d816..c3c9979 100644
--- a/src/hooks/useNeeds.ts
+++ b/src/hooks/useNeeds.ts
@@ -25,6 +25,7 @@ const sanitizeNeeds = (needs: Supply[]): Supply[] =>
status: need.status,
pickup: need.pickup,
loanable: need.loanable,
+ createdAt: need.createdAt || new Date().toISOString(),
...(need.loanable ? { returnDate: need.returnDate } : {}),
}));
@@ -39,6 +40,8 @@ const useNeeds = (): UseNeedsHook => {
loanable = false,
returnDate?: string
): Promise => {
+ const createdAt = new Date().toISOString();
+
const newNeed: Supply = {
itemName,
quantityNeeded,
@@ -46,15 +49,24 @@ const useNeeds = (): UseNeedsHook => {
providedBy: [],
status: false,
pickup,
+ createdAt,
loanable,
...(loanable && returnDate ? { returnDate } : {}),
};
try {
+ console.log('Before sanitizeNeeds:', [
+ ...(organization.needs || []),
+ newNeed,
+ ]);
const updatedNeeds = sanitizeNeeds([
...(organization.needs || []),
newNeed,
]);
+ console.log('After sanitizeNeeds:', [
+ ...(organization.needs || []),
+ newNeed,
+ ]);
await updateProfile({ needs: updatedNeeds });
console.log(`Successfully added new need: ${itemName}`);
} catch (error_) {
@@ -82,7 +94,12 @@ const useNeeds = (): UseNeedsHook => {
}
const updatedNeeds = [...organization.needs];
- updatedNeeds[needIndex] = { ...updatedNeeds[needIndex], ...updates };
+ updatedNeeds[needIndex] = {
+ ...updatedNeeds[needIndex],
+ ...updates,
+ // Set status to false if quantityNeeded is greater than 0
+ status: updates.quantityNeeded ? false : updatedNeeds[needIndex].status,
+ };
const sanitizedNeeds = sanitizeNeeds(updatedNeeds);
await updateProfile({ needs: sanitizedNeeds });
diff --git a/src/stores/userStore.ts b/src/stores/userStore.ts
index d17ad82..869c796 100644
--- a/src/stores/userStore.ts
+++ b/src/stores/userStore.ts
@@ -37,6 +37,8 @@ const useUserStore = create()(
}
} else {
set({ user: undefined });
+ const eventStore = useEventStore.getState();
+ eventStore.setEvents([]);
}
set({ loading: false });
}
diff --git a/src/types/user.d.ts b/src/types/user.d.ts
index 1120a2e..57c049a 100644
--- a/src/types/user.d.ts
+++ b/src/types/user.d.ts
@@ -18,6 +18,7 @@ interface Supply {
status: boolean;
pickup: boolean;
loanable: boolean;
+ createdAt: string;
returnDate?: string;
}
diff --git a/src/utils/eventsUtils.ts b/src/utils/eventsUtils.ts
index a44abbf..2fd5387 100644
--- a/src/utils/eventsUtils.ts
+++ b/src/utils/eventsUtils.ts
@@ -54,6 +54,7 @@ const createDonationEvent = (
pickup: method === 'pick-up',
loanable: supply?.loanable || false,
returnDate: supply?.loanable ? supply.returnDate || '' : '',
+ createdAt: new Date().toISOString(),
};
}),
});