add handling of no stores and fix app naming
All checks were successful
Build & Deploy Costco Grocery List / build (push) Successful in 11s
Build & Deploy Costco Grocery List / verify-images (push) Successful in 2s
Build & Deploy Costco Grocery List / deploy (push) Successful in 5s
Build & Deploy Costco Grocery List / notify (push) Successful in 1s

This commit is contained in:
Nico 2026-01-28 01:06:19 -08:00
parent e9b678c7be
commit dfaab1dfcb
4 changed files with 113 additions and 17 deletions

View File

@ -1,12 +1,15 @@
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Costco Grocery List</title> <title>Grocery App</title>
</head> </head>
<body> <body>
<div id="root"></div> <div id="root"></div>
<script type="module" src="/src/main.tsx"></script> <script type="module" src="/src/main.tsx"></script>
</body> </body>
</html> </html>

View File

@ -1,4 +1,5 @@
import { useCallback, useContext, useEffect, useMemo, useState } from "react"; import { useCallback, useContext, useEffect, useMemo, useState } from "react";
import { useNavigate } from "react-router-dom";
import { import {
addItem, addItem,
getClassification, getClassification,
@ -30,13 +31,16 @@ import { findSimilarItems } from "../utils/stringSimilarity";
export default function GroceryList() { export default function GroceryList() {
const pageTitle = "Grocery List";
const { role: systemRole } = useContext(AuthContext); const { role: systemRole } = useContext(AuthContext);
const { activeHousehold } = useContext(HouseholdContext); const { activeHousehold } = useContext(HouseholdContext);
const { activeStore } = useContext(StoreContext); const { activeStore, stores, loading: storeLoading } = useContext(StoreContext);
const { settings } = useContext(SettingsContext); const { settings } = useContext(SettingsContext);
const navigate = useNavigate();
// Get household role for permissions // Get household role for permissions
const householdRole = activeHousehold?.role; const householdRole = activeHousehold?.role;
const isHouseholdAdmin = householdRole === "admin";
// === State === // // === State === //
const [items, setItems] = useState([]); const [items, setItems] = useState([]);
@ -494,13 +498,68 @@ export default function GroceryList() {
}; };
if (!activeHousehold || !activeStore) { if (!activeHousehold) {
return ( return (
<div className="glist-body"> <div className="glist-body">
<div className="glist-container"> <div className="glist-container">
<h1 className="glist-title">Costco Grocery List</h1> <h1 className="glist-title">{pageTitle}</h1>
<p style={{ textAlign: 'center', marginTop: '2rem', color: 'var(--text-secondary)' }}> <p style={{ textAlign: 'center', marginTop: '2rem', color: 'var(--text-secondary)' }}>
{!activeHousehold ? 'Loading households...' : 'Loading stores...'} Loading households...
</p>
</div>
</div>
);
}
if (storeLoading) {
return (
<div className="glist-body">
<div className="glist-container">
<h1 className="glist-title">{pageTitle}</h1>
<p style={{ textAlign: 'center', marginTop: '2rem', color: 'var(--text-secondary)' }}>
Loading stores...
</p>
</div>
</div>
);
}
if (!storeLoading && stores.length === 0) {
return (
<div className="glist-body">
<div className="glist-container">
<h1 className="glist-title">{pageTitle}</h1>
<div className="glist-empty-state">
<h2 className="glist-empty-title">No stores found</h2>
<p className="glist-empty-text">
This household doesnt have any stores yet.
</p>
{isHouseholdAdmin ? (
<button
className="btn-primary"
onClick={() => navigate("/manage?tab=stores")}
>
Go to Manage Stores
</button>
) : (
<p className="glist-empty-text">
Please notify a household admin to add a store.
</p>
)}
</div>
</div>
</div>
);
}
if (!activeStore) {
return (
<div className="glist-body">
<div className="glist-container">
<h1 className="glist-title">{pageTitle}</h1>
<StoreTabs />
<p style={{ textAlign: 'center', marginTop: '2rem', color: 'var(--text-secondary)' }}>
Loading stores...
</p> </p>
</div> </div>
</div> </div>
@ -511,7 +570,7 @@ export default function GroceryList() {
return ( return (
<div className="glist-body"> <div className="glist-body">
<div className="glist-container"> <div className="glist-container">
<h1 className="glist-title">Costco Grocery List</h1> <h1 className="glist-title">{pageTitle}</h1>
<StoreTabs /> <StoreTabs />
<p style={{ textAlign: 'center', marginTop: '2rem' }}>Loading grocery list...</p> <p style={{ textAlign: 'center', marginTop: '2rem' }}>Loading grocery list...</p>
</div> </div>
@ -523,7 +582,7 @@ export default function GroceryList() {
return ( return (
<div className="glist-body"> <div className="glist-body">
<div className="glist-container"> <div className="glist-container">
<h1 className="glist-title">Costco Grocery List</h1> <h1 className="glist-title">{pageTitle}</h1>
<StoreTabs /> <StoreTabs />

View File

@ -1,4 +1,5 @@
import { useContext, useState } from "react"; import { useContext, useEffect, useState } from "react";
import { useSearchParams } from "react-router-dom";
import ManageHousehold from "../components/manage/ManageHousehold"; import ManageHousehold from "../components/manage/ManageHousehold";
import ManageStores from "../components/manage/ManageStores"; import ManageStores from "../components/manage/ManageStores";
import { HouseholdContext } from "../context/HouseholdContext"; import { HouseholdContext } from "../context/HouseholdContext";
@ -7,6 +8,14 @@ import "../styles/pages/Manage.css";
export default function Manage() { export default function Manage() {
const { activeHousehold } = useContext(HouseholdContext); const { activeHousehold } = useContext(HouseholdContext);
const [activeTab, setActiveTab] = useState("household"); const [activeTab, setActiveTab] = useState("household");
const [searchParams] = useSearchParams();
useEffect(() => {
const tab = searchParams.get("tab");
if (tab === "household" || tab === "stores") {
setActiveTab(tab);
}
}, [searchParams]);
if (!activeHousehold) { if (!activeHousehold) {
return ( return (

View File

@ -64,6 +64,31 @@
padding-top: var(--spacing-md); padding-top: var(--spacing-md);
} }
/* Empty State */
.glist-empty-state {
margin: var(--spacing-xl) auto;
padding: var(--spacing-lg);
text-align: center;
display: flex;
flex-direction: column;
gap: var(--spacing-md);
max-width: 32rem;
border: var(--border-width-thin) dashed var(--color-border-medium);
border-radius: var(--border-radius-lg);
background: var(--color-bg-surface);
}
.glist-empty-title {
font-size: var(--font-size-lg);
color: var(--color-text-primary);
margin: 0;
}
.glist-empty-text {
color: var(--color-text-secondary);
margin: 0;
}
.glist-section-header .glist-section-title { .glist-section-header .glist-section-title {
margin: 0; margin: 0;
border: none; border: none;