Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions apps/frontend/src/components/PageEmptyState.tsx
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

our files are named in camelCase~ could you rename the empty state files accordingly?

Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import React from 'react';
import { Box, Button } from '@chakra-ui/react';
import { CircleCheck } from 'lucide-react';
import { useNavigate } from 'react-router-dom';

interface PageEmptyStateProps {
entity?: string;
subtitle?: string;
primaryButtonText: string;
primaryButtonLink: string;
secondaryButtonText: string;
secondaryButtonLink: string;
}

const PageEmptyState: React.FC<PageEmptyStateProps> = ({
entity,
subtitle,
primaryButtonText,
primaryButtonLink,
secondaryButtonText,
secondaryButtonLink,
}) => {
const navigate = useNavigate();
const message = subtitle ?? `You have no ${entity} at this time`;

return (
<Box
display="flex"
flexDirection="column"
alignItems="center"
justifyContent="center"
textAlign="center"
fontFamily="'Inter', sans-serif"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

our repo theme already maps this line using fonts.inter - can you use that instead?

fontSize="sm"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we also have project pixel defined scale values of textStyle = "p/ p2/ p3/ h4" ~ consider which one would fit the best for this size!

color="neutral.600"
py={10}
gap={2}
>
<Box mb={2}>
<CircleCheck size={24} color="#262626" />
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use neutral.800 here!

</Box>
<Box fontWeight="600" fontSize="lg" color="neutral.800">
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use for text content - it's also what makes the testStyle work!

Nothing to see here.
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit - designs end with exclamation mark instead of period

</Box>
<Box color="neutral.700" fontWeight="400">
{message}
</Box>
<Box display="flex" gap={3} mt={4}>
<Button
bg="neutral.700"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we make this size=sm to match the thinner designed buttons?

nice job using our repo frontend values here~

color="white"
_hover={{ bg: 'neutral.800' }}
onClick={() => navigate(primaryButtonLink)}
>
{primaryButtonText}
</Button>
<Button
bg="neutral.300"
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add size="sm" here too
remove bg color -> white/clear in design
define the button by setting it to outline & defining border color.

good example is the outline button in adminOrderManagement.tsx:387-394 - take their outline and border (variant="outline", borderColor="") use neutral.200 as border color.

color="neutral.700"
_hover={{ bg: 'neutral.50' }}
onClick={() => navigate(secondaryButtonLink)}
>
{secondaryButtonText}
</Button>
</Box>
</Box>
);
};

export default PageEmptyState;
37 changes: 37 additions & 0 deletions apps/frontend/src/components/SectionEmptyState.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Box } from '@chakra-ui/react';
import { CircleCheck } from 'lucide-react';

interface EmptyStateProps {
entity?: string;
subtitle?: string;
}

const SectionEmptyState: React.FC<EmptyStateProps> = ({ entity, subtitle }) => {
const message = subtitle ?? `You have no ${entity} at this time.`;
return (
<Box
display="flex"
flexDirection="column"
alignItems="center"
justifyContent="center"
textAlign="center"
fontFamily="'Inter', sans-serif"
fontSize="sm"
color="neutral.600"
py={10}
gap={2}
>
<Box mb={2}>
<CircleCheck size={24} color="#262626" />
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: designs don't have the circle check for section empty states!

</Box>
<Box fontWeight="600" fontSize="lg" color="neutral.800">
Nothing to see here.
</Box>
<Box color="neutral.700" fontWeight="400">
{message}
</Box>
</Box>
);
};

export default SectionEmptyState;
208 changes: 132 additions & 76 deletions apps/frontend/src/containers/adminDashboard.tsx
Copy link
Copy Markdown

@jiang-h-y jiang-h-y May 27, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make sure you implement the empty-state dashboard for FMs too

Edit: Sorry, just realized the FM dashboard isn't merged yet. Not sure if we want to wait to merge this PR until we can also add an empty state for the FM dashboard.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes fm dashboard should be in soon~ let's wait to merge until we can add empty state there!

Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import { useAlert } from '../hooks/alert';
import { FloatingAlert } from '@components/floatingAlert';
import { useNavigate } from 'react-router-dom';
import { ROUTES } from '../routes';
import SectionEmptyState from '@components/SectionEmptyState';
import PageEmptyState from '@components/PageEmptyState';

const AdminDashboard: React.FC = () => {
const navigate = useNavigate();
Expand Down Expand Up @@ -84,6 +86,11 @@ const AdminDashboard: React.FC = () => {
fetchPendingApplications();
}, [setAlertMessage]);

const isPageEmpty =
pendingApplications.length === 0 &&
recentOrders.length === 0 &&
recentDonations.length === 0;

return (
<Box p={12}>
{alertState && (
Expand All @@ -98,84 +105,133 @@ const AdminDashboard: React.FC = () => {
Welcome, {currentUser?.firstName} {currentUser?.lastName}
</Heading>

<Text textStyle="p" color="gray.light" fontWeight={600} mb={4}>
Pending Actions
</Text>
<Box display="grid" gridTemplateColumns="repeat(2, 1fr)" gap={4} mb={16}>
{pendingApplications.map((application) => (
<DashboardCard
type={DashboardCardType.ACTION}
title={application.name}
date={application.dateApplied}
key={application.id}
linkText="View Application Details"
badge={{
label:
application.type === 'pantry' ? 'Pantry' : 'Food Manufacturer',
bg: 'neutral.100',
color: 'neutral.600',
}}
onLinkClick={() => {
navigate(
application.type === 'pantry'
? ROUTES.PANTRY_MANAGEMENT_DETAILS.replace(
':pantryId',
application.id.toString(),
)
: ROUTES.FOOD_MANUFACTURER_APPLICATION_DETAILS.replace(
':applicationId',
application.id.toString(),
),
);
}}
/>
))}
</Box>
{isPageEmpty ? (
<PageEmptyState
entity="orders or applications to review"
primaryButtonText="View Pantries"
primaryButtonLink={ROUTES.PANTRY_MANAGEMENT}
secondaryButtonText="View Donations"
secondaryButtonLink={ROUTES.ADMIN_DONATION}
/>
) : (
<>
<Text textStyle="p" color="gray.light" fontWeight={600} mb={4}>
Pending Actions
</Text>
{pendingApplications.length === 0 ? (
<Box mb={16}>
<SectionEmptyState entity="pending applications" />
</Box>
) : (
<Box
display="grid"
gridTemplateColumns="repeat(2, 1fr)"
gap={4}
mb={16}
>
{pendingApplications.map((application) => (
<DashboardCard
type={DashboardCardType.ACTION}
title={application.name}
date={application.dateApplied}
key={application.id}
linkText="View Application Details"
badge={{
label:
application.type === 'pantry'
? 'Pantry'
: 'Food Manufacturer',
bg: 'neutral.100',
color: 'neutral.600',
}}
onLinkClick={() => {
navigate(
application.type === 'pantry'
? ROUTES.PANTRY_MANAGEMENT_DETAILS.replace(
':pantryId',
application.id.toString(),
)
: ROUTES.FOOD_MANUFACTURER_APPLICATION_DETAILS.replace(
':applicationId',
application.id.toString(),
),
);
}}
/>
))}
</Box>
)}

<Text textStyle="p" color="gray.light" fontWeight={600} mb={4}>
Recent Orders
</Text>
<Box display="grid" gridTemplateColumns="repeat(2, 1fr)" gap={4} mb={16}>
{recentOrders.map((order) => (
<DashboardCard
key={order.orderId}
type={DashboardCardType.ORDER}
title={`Order #${order.orderId}`}
date={order.createdAt}
subtitle={order.request.pantry.pantryName}
linkText="View Order Details"
badge={ORDER_STATUS_BADGE[order.status]}
assignee={{
id: order.assignee.id,
firstName: order.assignee.firstName,
lastName: order.assignee.lastName,
}}
onLinkClick={() =>
navigate(`/admin-order-management?orderId=${order.orderId}`)
}
/>
))}
</Box>
<Text textStyle="p" color="gray.light" fontWeight={600} mb={4}>
Recent Orders
</Text>
{recentOrders.length === 0 ? (
<Box mb={16}>
<SectionEmptyState entity="recent orders" />
</Box>
) : (
<Box
display="grid"
gridTemplateColumns="repeat(2, 1fr)"
gap={4}
mb={16}
>
{recentOrders.map((order) => (
<DashboardCard
key={order.orderId}
type={DashboardCardType.ORDER}
title={`Order #${order.orderId}`}
date={order.createdAt}
subtitle={order.request.pantry.pantryName}
linkText="View Order Details"
badge={ORDER_STATUS_BADGE[order.status]}
assignee={{
id: order.assignee.id,
firstName: order.assignee.firstName,
lastName: order.assignee.lastName,
}}
onLinkClick={() =>
navigate(`/admin-order-management?orderId=${order.orderId}`)
}
/>
))}
</Box>
)}

<Text textStyle="p" color="gray.light" fontWeight={600} mb={4}>
Recent Donations
</Text>
<Box display="grid" gridTemplateColumns="repeat(2, 1fr)" gap={4} mb={16}>
{recentDonations.map((donation) => (
<DashboardCard
key={donation.donationId}
type={DashboardCardType.RECENT_DONATION}
title={`Donation #${donation.donationId}`}
date={donation.dateDonated}
subtitle={donation.foodManufacturer?.foodManufacturerName}
linkText="View Donation Details"
badge={DONATION_STATUS_BADGE[donation.status]}
onLinkClick={() =>
navigate(`/admin-donation?donationId=${donation.donationId}`)
}
/>
))}
</Box>
<Text textStyle="p" color="gray.light" fontWeight={600} mb={4}>
Recent Donations
</Text>
{recentDonations.length === 0 ? (
<Box mb={16}>
<SectionEmptyState entity="recent donations" />
</Box>
) : (
<Box
display="grid"
gridTemplateColumns="repeat(2, 1fr)"
gap={4}
mb={16}
>
{recentDonations.map((donation) => (
<DashboardCard
key={donation.donationId}
type={DashboardCardType.RECENT_DONATION}
title={`Donation #${donation.donationId}`}
date={donation.dateDonated}
subtitle={donation.foodManufacturer?.foodManufacturerName}
linkText="View Donation Details"
badge={DONATION_STATUS_BADGE[donation.status]}
onLinkClick={() =>
navigate(
`/admin-donation?donationId=${donation.donationId}`,
)
}
/>
))}
</Box>
)}
</>
)}
</Box>
);
};
Expand Down
Loading
Loading